feat(ui): add configurable role label display and syntax highlighting support
- Introduce `RoleLabelDisplay` enum (inline, above, none) and integrate it into UI rendering and message formatting. - Replace `show_role_labels` boolean with `role_label_mode` across config, formatter, session, and TUI components. - Add `syntax_highlighting` boolean to UI settings with default `false` and support in message rendering. - Update configuration schema version to 1.3.0 and provide deserialization handling for legacy boolean values. - Extend theme definitions with code block styling fields (background, border, text, keyword, string, comment) and default values in `Theme`. - Adjust related modules (`formatting.rs`, `ui.rs`, `session.rs`, `chat_app.rs`) to use the new settings and theme fields.
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
use crate::ProviderConfig;
|
||||
use crate::Result;
|
||||
use crate::mode::ModeConfig;
|
||||
use crate::ui::RoleLabelDisplay;
|
||||
use serde::de::{self, Deserializer, Visitor};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
@@ -11,7 +14,7 @@ use std::time::Duration;
|
||||
pub const DEFAULT_CONFIG_PATH: &str = "~/.config/owlen/config.toml";
|
||||
|
||||
/// Current schema version written to `config.toml`.
|
||||
pub const CONFIG_SCHEMA_VERSION: &str = "1.2.0";
|
||||
pub const CONFIG_SCHEMA_VERSION: &str = "1.3.0";
|
||||
|
||||
/// Core configuration shared by all OWLEN clients
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -700,8 +703,13 @@ pub struct UiSettings {
|
||||
pub word_wrap: bool,
|
||||
#[serde(default = "UiSettings::default_max_history_lines")]
|
||||
pub max_history_lines: usize,
|
||||
#[serde(default = "UiSettings::default_show_role_labels")]
|
||||
pub show_role_labels: bool,
|
||||
#[serde(
|
||||
rename = "role_label",
|
||||
alias = "show_role_labels",
|
||||
default = "UiSettings::default_role_label_mode",
|
||||
deserialize_with = "UiSettings::deserialize_role_label_mode"
|
||||
)]
|
||||
pub role_label_mode: RoleLabelDisplay,
|
||||
#[serde(default = "UiSettings::default_wrap_column")]
|
||||
pub wrap_column: u16,
|
||||
#[serde(default = "UiSettings::default_show_onboarding")]
|
||||
@@ -712,6 +720,8 @@ pub struct UiSettings {
|
||||
pub scrollback_lines: usize,
|
||||
#[serde(default = "UiSettings::default_show_cursor_outside_insert")]
|
||||
pub show_cursor_outside_insert: bool,
|
||||
#[serde(default = "UiSettings::default_syntax_highlighting")]
|
||||
pub syntax_highlighting: bool,
|
||||
}
|
||||
|
||||
impl UiSettings {
|
||||
@@ -727,8 +737,8 @@ impl UiSettings {
|
||||
2000
|
||||
}
|
||||
|
||||
fn default_show_role_labels() -> bool {
|
||||
true
|
||||
const fn default_role_label_mode() -> RoleLabelDisplay {
|
||||
RoleLabelDisplay::Above
|
||||
}
|
||||
|
||||
fn default_wrap_column() -> u16 {
|
||||
@@ -750,6 +760,62 @@ impl UiSettings {
|
||||
const fn default_show_cursor_outside_insert() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
const fn default_syntax_highlighting() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn deserialize_role_label_mode<'de, D>(
|
||||
deserializer: D,
|
||||
) -> std::result::Result<RoleLabelDisplay, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct RoleLabelModeVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for RoleLabelModeVisitor {
|
||||
type Value = RoleLabelDisplay;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("`inline`, `above`, `none`, or a legacy boolean")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
match v.trim().to_ascii_lowercase().as_str() {
|
||||
"inline" => Ok(RoleLabelDisplay::Inline),
|
||||
"above" => Ok(RoleLabelDisplay::Above),
|
||||
"none" => Ok(RoleLabelDisplay::None),
|
||||
other => Err(de::Error::unknown_variant(
|
||||
other,
|
||||
&["inline", "above", "none"],
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, v: String) -> std::result::Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
self.visit_str(&v)
|
||||
}
|
||||
|
||||
fn visit_bool<E>(self, v: bool) -> std::result::Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
if v {
|
||||
Ok(RoleLabelDisplay::Above)
|
||||
} else {
|
||||
Ok(RoleLabelDisplay::None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(RoleLabelModeVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UiSettings {
|
||||
@@ -758,12 +824,13 @@ impl Default for UiSettings {
|
||||
theme: Self::default_theme(),
|
||||
word_wrap: Self::default_word_wrap(),
|
||||
max_history_lines: Self::default_max_history_lines(),
|
||||
show_role_labels: Self::default_show_role_labels(),
|
||||
role_label_mode: Self::default_role_label_mode(),
|
||||
wrap_column: Self::default_wrap_column(),
|
||||
show_onboarding: Self::default_show_onboarding(),
|
||||
input_max_rows: Self::default_input_max_rows(),
|
||||
scrollback_lines: Self::default_scrollback_lines(),
|
||||
show_cursor_outside_insert: Self::default_show_cursor_outside_insert(),
|
||||
syntax_highlighting: Self::default_syntax_highlighting(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -919,7 +986,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn expand_provider_env_vars_resolves_api_key() {
|
||||
std::env::set_var("OWLEN_TEST_API_KEY", "super-secret");
|
||||
unsafe {
|
||||
std::env::set_var("OWLEN_TEST_API_KEY", "super-secret");
|
||||
}
|
||||
|
||||
let mut config = Config::default();
|
||||
if let Some(ollama) = config.providers.get_mut("ollama") {
|
||||
@@ -935,12 +1004,16 @@ mod tests {
|
||||
Some("super-secret")
|
||||
);
|
||||
|
||||
std::env::remove_var("OWLEN_TEST_API_KEY");
|
||||
unsafe {
|
||||
std::env::remove_var("OWLEN_TEST_API_KEY");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_provider_env_vars_errors_for_missing_variable() {
|
||||
std::env::remove_var("OWLEN_TEST_MISSING");
|
||||
unsafe {
|
||||
std::env::remove_var("OWLEN_TEST_MISSING");
|
||||
}
|
||||
|
||||
let mut config = Config::default();
|
||||
if let Some(ollama) = config.providers.get_mut("ollama") {
|
||||
|
||||
Reference in New Issue
Block a user