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::ProviderConfig;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::mode::ModeConfig;
|
use crate::mode::ModeConfig;
|
||||||
|
use crate::ui::RoleLabelDisplay;
|
||||||
|
use serde::de::{self, Deserializer, Visitor};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fmt;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -11,7 +14,7 @@ use std::time::Duration;
|
|||||||
pub const DEFAULT_CONFIG_PATH: &str = "~/.config/owlen/config.toml";
|
pub const DEFAULT_CONFIG_PATH: &str = "~/.config/owlen/config.toml";
|
||||||
|
|
||||||
/// Current schema version written to `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
|
/// Core configuration shared by all OWLEN clients
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@@ -700,8 +703,13 @@ pub struct UiSettings {
|
|||||||
pub word_wrap: bool,
|
pub word_wrap: bool,
|
||||||
#[serde(default = "UiSettings::default_max_history_lines")]
|
#[serde(default = "UiSettings::default_max_history_lines")]
|
||||||
pub max_history_lines: usize,
|
pub max_history_lines: usize,
|
||||||
#[serde(default = "UiSettings::default_show_role_labels")]
|
#[serde(
|
||||||
pub show_role_labels: bool,
|
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")]
|
#[serde(default = "UiSettings::default_wrap_column")]
|
||||||
pub wrap_column: u16,
|
pub wrap_column: u16,
|
||||||
#[serde(default = "UiSettings::default_show_onboarding")]
|
#[serde(default = "UiSettings::default_show_onboarding")]
|
||||||
@@ -712,6 +720,8 @@ pub struct UiSettings {
|
|||||||
pub scrollback_lines: usize,
|
pub scrollback_lines: usize,
|
||||||
#[serde(default = "UiSettings::default_show_cursor_outside_insert")]
|
#[serde(default = "UiSettings::default_show_cursor_outside_insert")]
|
||||||
pub show_cursor_outside_insert: bool,
|
pub show_cursor_outside_insert: bool,
|
||||||
|
#[serde(default = "UiSettings::default_syntax_highlighting")]
|
||||||
|
pub syntax_highlighting: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiSettings {
|
impl UiSettings {
|
||||||
@@ -727,8 +737,8 @@ impl UiSettings {
|
|||||||
2000
|
2000
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_show_role_labels() -> bool {
|
const fn default_role_label_mode() -> RoleLabelDisplay {
|
||||||
true
|
RoleLabelDisplay::Above
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_wrap_column() -> u16 {
|
fn default_wrap_column() -> u16 {
|
||||||
@@ -750,6 +760,62 @@ impl UiSettings {
|
|||||||
const fn default_show_cursor_outside_insert() -> bool {
|
const fn default_show_cursor_outside_insert() -> bool {
|
||||||
false
|
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 {
|
impl Default for UiSettings {
|
||||||
@@ -758,12 +824,13 @@ impl Default for UiSettings {
|
|||||||
theme: Self::default_theme(),
|
theme: Self::default_theme(),
|
||||||
word_wrap: Self::default_word_wrap(),
|
word_wrap: Self::default_word_wrap(),
|
||||||
max_history_lines: Self::default_max_history_lines(),
|
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(),
|
wrap_column: Self::default_wrap_column(),
|
||||||
show_onboarding: Self::default_show_onboarding(),
|
show_onboarding: Self::default_show_onboarding(),
|
||||||
input_max_rows: Self::default_input_max_rows(),
|
input_max_rows: Self::default_input_max_rows(),
|
||||||
scrollback_lines: Self::default_scrollback_lines(),
|
scrollback_lines: Self::default_scrollback_lines(),
|
||||||
show_cursor_outside_insert: Self::default_show_cursor_outside_insert(),
|
show_cursor_outside_insert: Self::default_show_cursor_outside_insert(),
|
||||||
|
syntax_highlighting: Self::default_syntax_highlighting(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -919,7 +986,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expand_provider_env_vars_resolves_api_key() {
|
fn expand_provider_env_vars_resolves_api_key() {
|
||||||
|
unsafe {
|
||||||
std::env::set_var("OWLEN_TEST_API_KEY", "super-secret");
|
std::env::set_var("OWLEN_TEST_API_KEY", "super-secret");
|
||||||
|
}
|
||||||
|
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
if let Some(ollama) = config.providers.get_mut("ollama") {
|
if let Some(ollama) = config.providers.get_mut("ollama") {
|
||||||
@@ -935,12 +1004,16 @@ mod tests {
|
|||||||
Some("super-secret")
|
Some("super-secret")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
std::env::remove_var("OWLEN_TEST_API_KEY");
|
std::env::remove_var("OWLEN_TEST_API_KEY");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn expand_provider_env_vars_errors_for_missing_variable() {
|
fn expand_provider_env_vars_errors_for_missing_variable() {
|
||||||
|
unsafe {
|
||||||
std::env::remove_var("OWLEN_TEST_MISSING");
|
std::env::remove_var("OWLEN_TEST_MISSING");
|
||||||
|
}
|
||||||
|
|
||||||
let mut config = Config::default();
|
let mut config = Config::default();
|
||||||
if let Some(ollama) = config.providers.get_mut("ollama") {
|
if let Some(ollama) = config.providers.get_mut("ollama") {
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
use crate::types::Message;
|
use crate::types::Message;
|
||||||
|
use crate::ui::RoleLabelDisplay;
|
||||||
|
|
||||||
/// Formats messages for display across different clients.
|
/// Formats messages for display across different clients.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MessageFormatter {
|
pub struct MessageFormatter {
|
||||||
wrap_width: usize,
|
wrap_width: usize,
|
||||||
show_role_labels: bool,
|
role_label_mode: RoleLabelDisplay,
|
||||||
preserve_empty_lines: bool,
|
preserve_empty_lines: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageFormatter {
|
impl MessageFormatter {
|
||||||
/// Create a new formatter
|
/// Create a new formatter
|
||||||
pub fn new(wrap_width: usize, show_role_labels: bool) -> Self {
|
pub fn new(wrap_width: usize, role_label_mode: RoleLabelDisplay) -> Self {
|
||||||
Self {
|
Self {
|
||||||
wrap_width: wrap_width.max(20),
|
wrap_width: wrap_width.max(20),
|
||||||
show_role_labels,
|
role_label_mode,
|
||||||
preserve_empty_lines: false,
|
preserve_empty_lines: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,9 +30,19 @@ impl MessageFormatter {
|
|||||||
self.wrap_width = width.max(20);
|
self.wrap_width = width.max(20);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether role labels should be shown alongside messages
|
/// The configured role label layout preference.
|
||||||
|
pub fn role_label_mode(&self) -> RoleLabelDisplay {
|
||||||
|
self.role_label_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether any role label should be shown alongside messages.
|
||||||
pub fn show_role_labels(&self) -> bool {
|
pub fn show_role_labels(&self) -> bool {
|
||||||
self.show_role_labels
|
!matches!(self.role_label_mode, RoleLabelDisplay::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the role label layout preference.
|
||||||
|
pub fn set_role_label_mode(&mut self, mode: RoleLabelDisplay) {
|
||||||
|
self.role_label_mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_message(&self, message: &Message) -> Vec<String> {
|
pub fn format_message(&self, message: &Message) -> Vec<String> {
|
||||||
|
|||||||
@@ -1031,13 +1031,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolve_api_key_expands_env_var() {
|
fn resolve_api_key_expands_env_var() {
|
||||||
|
unsafe {
|
||||||
std::env::set_var("OLLAMA_TEST_KEY", "secret");
|
std::env::set_var("OLLAMA_TEST_KEY", "secret");
|
||||||
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve_api_key(Some("${OLLAMA_TEST_KEY}".into())),
|
resolve_api_key(Some("${OLLAMA_TEST_KEY}".into())),
|
||||||
Some("secret".into())
|
Some("secret".into())
|
||||||
);
|
);
|
||||||
|
unsafe {
|
||||||
std::env::remove_var("OLLAMA_TEST_KEY");
|
std::env::remove_var("OLLAMA_TEST_KEY");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn normalize_base_url_removes_api_path() {
|
fn normalize_base_url_removes_api_path() {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use crate::storage::{SessionMeta, StorageManager};
|
|||||||
use crate::types::{
|
use crate::types::{
|
||||||
ChatParameters, ChatRequest, ChatResponse, Conversation, Message, ModelInfo, ToolCall,
|
ChatParameters, ChatRequest, ChatResponse, Conversation, Message, ModelInfo, ToolCall,
|
||||||
};
|
};
|
||||||
use crate::ui::UiController;
|
use crate::ui::{RoleLabelDisplay, UiController};
|
||||||
use crate::validation::{SchemaValidator, get_builtin_schemas};
|
use crate::validation::{SchemaValidator, get_builtin_schemas};
|
||||||
use crate::{ChatStream, Provider};
|
use crate::{ChatStream, Provider};
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -264,7 +264,7 @@ impl SessionController {
|
|||||||
);
|
);
|
||||||
let formatter = MessageFormatter::new(
|
let formatter = MessageFormatter::new(
|
||||||
config_guard.ui.wrap_column as usize,
|
config_guard.ui.wrap_column as usize,
|
||||||
config_guard.ui.show_role_labels,
|
config_guard.ui.role_label_mode,
|
||||||
)
|
)
|
||||||
.with_preserve_empty(config_guard.ui.word_wrap);
|
.with_preserve_empty(config_guard.ui.word_wrap);
|
||||||
let input_buffer = InputBuffer::new(
|
let input_buffer = InputBuffer::new(
|
||||||
@@ -351,6 +351,10 @@ impl SessionController {
|
|||||||
self.formatter.set_wrap_width(width);
|
self.formatter.set_wrap_width(width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_role_label_mode(&mut self, mode: RoleLabelDisplay) {
|
||||||
|
self.formatter.set_role_label_mode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
// Asynchronous access to the configuration (used internally).
|
// Asynchronous access to the configuration (used internally).
|
||||||
pub async fn config_async(&self) -> tokio::sync::MutexGuard<'_, Config> {
|
pub async fn config_async(&self) -> tokio::sync::MutexGuard<'_, Config> {
|
||||||
self.config.lock().await
|
self.config.lock().await
|
||||||
|
|||||||
@@ -116,6 +116,42 @@ pub struct Theme {
|
|||||||
#[serde(serialize_with = "serialize_color")]
|
#[serde(serialize_with = "serialize_color")]
|
||||||
pub cursor: Color,
|
pub cursor: Color,
|
||||||
|
|
||||||
|
/// Code block background color
|
||||||
|
#[serde(default = "Theme::default_code_block_background")]
|
||||||
|
#[serde(deserialize_with = "deserialize_color")]
|
||||||
|
#[serde(serialize_with = "serialize_color")]
|
||||||
|
pub code_block_background: Color,
|
||||||
|
|
||||||
|
/// Code block border color
|
||||||
|
#[serde(default = "Theme::default_code_block_border")]
|
||||||
|
#[serde(deserialize_with = "deserialize_color")]
|
||||||
|
#[serde(serialize_with = "serialize_color")]
|
||||||
|
pub code_block_border: Color,
|
||||||
|
|
||||||
|
/// Code block text color
|
||||||
|
#[serde(default = "Theme::default_code_block_text")]
|
||||||
|
#[serde(deserialize_with = "deserialize_color")]
|
||||||
|
#[serde(serialize_with = "serialize_color")]
|
||||||
|
pub code_block_text: Color,
|
||||||
|
|
||||||
|
/// Code block keyword color
|
||||||
|
#[serde(default = "Theme::default_code_block_keyword")]
|
||||||
|
#[serde(deserialize_with = "deserialize_color")]
|
||||||
|
#[serde(serialize_with = "serialize_color")]
|
||||||
|
pub code_block_keyword: Color,
|
||||||
|
|
||||||
|
/// Code block string literal color
|
||||||
|
#[serde(default = "Theme::default_code_block_string")]
|
||||||
|
#[serde(deserialize_with = "deserialize_color")]
|
||||||
|
#[serde(serialize_with = "serialize_color")]
|
||||||
|
pub code_block_string: Color,
|
||||||
|
|
||||||
|
/// Code block comment color
|
||||||
|
#[serde(default = "Theme::default_code_block_comment")]
|
||||||
|
#[serde(deserialize_with = "deserialize_color")]
|
||||||
|
#[serde(serialize_with = "serialize_color")]
|
||||||
|
pub code_block_comment: Color,
|
||||||
|
|
||||||
/// Placeholder text color
|
/// Placeholder text color
|
||||||
#[serde(deserialize_with = "deserialize_color")]
|
#[serde(deserialize_with = "deserialize_color")]
|
||||||
#[serde(serialize_with = "serialize_color")]
|
#[serde(serialize_with = "serialize_color")]
|
||||||
@@ -217,6 +253,30 @@ impl Default for Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
|
const fn default_code_block_background() -> Color {
|
||||||
|
Color::Black
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_code_block_border() -> Color {
|
||||||
|
Color::Gray
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_code_block_text() -> Color {
|
||||||
|
Color::White
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_code_block_keyword() -> Color {
|
||||||
|
Color::Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_code_block_string() -> Color {
|
||||||
|
Color::LightGreen
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn default_code_block_comment() -> Color {
|
||||||
|
Color::DarkGray
|
||||||
|
}
|
||||||
|
|
||||||
const fn default_agent_thought() -> Color {
|
const fn default_agent_thought() -> Color {
|
||||||
Color::LightBlue
|
Color::LightBlue
|
||||||
}
|
}
|
||||||
@@ -430,6 +490,12 @@ fn default_dark() -> Theme {
|
|||||||
selection_bg: Color::LightBlue,
|
selection_bg: Color::LightBlue,
|
||||||
selection_fg: Color::Black,
|
selection_fg: Color::Black,
|
||||||
cursor: Color::Magenta,
|
cursor: Color::Magenta,
|
||||||
|
code_block_background: Color::Rgb(25, 25, 25),
|
||||||
|
code_block_border: Color::LightMagenta,
|
||||||
|
code_block_text: Color::White,
|
||||||
|
code_block_keyword: Color::Yellow,
|
||||||
|
code_block_string: Color::LightGreen,
|
||||||
|
code_block_comment: Color::Gray,
|
||||||
placeholder: Color::DarkGray,
|
placeholder: Color::DarkGray,
|
||||||
error: Color::Red,
|
error: Color::Red,
|
||||||
info: Color::LightGreen,
|
info: Color::LightGreen,
|
||||||
@@ -473,6 +539,12 @@ fn default_light() -> Theme {
|
|||||||
selection_bg: Color::Rgb(164, 200, 240),
|
selection_bg: Color::Rgb(164, 200, 240),
|
||||||
selection_fg: Color::Black,
|
selection_fg: Color::Black,
|
||||||
cursor: Color::Rgb(217, 95, 2),
|
cursor: Color::Rgb(217, 95, 2),
|
||||||
|
code_block_background: Color::Rgb(245, 245, 245),
|
||||||
|
code_block_border: Color::Rgb(142, 68, 173),
|
||||||
|
code_block_text: Color::Black,
|
||||||
|
code_block_keyword: Color::Rgb(181, 137, 0),
|
||||||
|
code_block_string: Color::Rgb(46, 139, 87),
|
||||||
|
code_block_comment: Color::Gray,
|
||||||
placeholder: Color::Gray,
|
placeholder: Color::Gray,
|
||||||
error: Color::Rgb(192, 57, 43),
|
error: Color::Rgb(192, 57, 43),
|
||||||
info: Color::Green,
|
info: Color::Green,
|
||||||
@@ -516,6 +588,12 @@ fn gruvbox() -> Theme {
|
|||||||
selection_bg: Color::Rgb(80, 73, 69),
|
selection_bg: Color::Rgb(80, 73, 69),
|
||||||
selection_fg: Color::Rgb(235, 219, 178),
|
selection_fg: Color::Rgb(235, 219, 178),
|
||||||
cursor: Color::Rgb(254, 128, 25),
|
cursor: Color::Rgb(254, 128, 25),
|
||||||
|
code_block_background: Color::Rgb(60, 56, 54),
|
||||||
|
code_block_border: Color::Rgb(124, 111, 100),
|
||||||
|
code_block_text: Color::Rgb(235, 219, 178),
|
||||||
|
code_block_keyword: Color::Rgb(250, 189, 47),
|
||||||
|
code_block_string: Color::Rgb(142, 192, 124),
|
||||||
|
code_block_comment: Color::Rgb(124, 111, 100),
|
||||||
placeholder: Color::Rgb(102, 92, 84),
|
placeholder: Color::Rgb(102, 92, 84),
|
||||||
error: Color::Rgb(251, 73, 52), // #fb4934
|
error: Color::Rgb(251, 73, 52), // #fb4934
|
||||||
info: Color::Rgb(184, 187, 38),
|
info: Color::Rgb(184, 187, 38),
|
||||||
@@ -559,6 +637,12 @@ fn dracula() -> Theme {
|
|||||||
selection_bg: Color::Rgb(68, 71, 90),
|
selection_bg: Color::Rgb(68, 71, 90),
|
||||||
selection_fg: Color::Rgb(248, 248, 242),
|
selection_fg: Color::Rgb(248, 248, 242),
|
||||||
cursor: Color::Rgb(255, 121, 198),
|
cursor: Color::Rgb(255, 121, 198),
|
||||||
|
code_block_background: Color::Rgb(68, 71, 90),
|
||||||
|
code_block_border: Color::Rgb(189, 147, 249),
|
||||||
|
code_block_text: Color::Rgb(248, 248, 242),
|
||||||
|
code_block_keyword: Color::Rgb(255, 121, 198),
|
||||||
|
code_block_string: Color::Rgb(80, 250, 123),
|
||||||
|
code_block_comment: Color::Rgb(98, 114, 164),
|
||||||
placeholder: Color::Rgb(98, 114, 164),
|
placeholder: Color::Rgb(98, 114, 164),
|
||||||
error: Color::Rgb(255, 85, 85), // #ff5555
|
error: Color::Rgb(255, 85, 85), // #ff5555
|
||||||
info: Color::Rgb(80, 250, 123),
|
info: Color::Rgb(80, 250, 123),
|
||||||
@@ -602,6 +686,12 @@ fn solarized() -> Theme {
|
|||||||
selection_bg: Color::Rgb(7, 54, 66),
|
selection_bg: Color::Rgb(7, 54, 66),
|
||||||
selection_fg: Color::Rgb(147, 161, 161),
|
selection_fg: Color::Rgb(147, 161, 161),
|
||||||
cursor: Color::Rgb(211, 54, 130),
|
cursor: Color::Rgb(211, 54, 130),
|
||||||
|
code_block_background: Color::Rgb(7, 54, 66),
|
||||||
|
code_block_border: Color::Rgb(38, 139, 210),
|
||||||
|
code_block_text: Color::Rgb(147, 161, 161),
|
||||||
|
code_block_keyword: Color::Rgb(181, 137, 0),
|
||||||
|
code_block_string: Color::Rgb(133, 153, 0),
|
||||||
|
code_block_comment: Color::Rgb(88, 110, 117),
|
||||||
placeholder: Color::Rgb(88, 110, 117),
|
placeholder: Color::Rgb(88, 110, 117),
|
||||||
error: Color::Rgb(220, 50, 47), // #dc322f (red)
|
error: Color::Rgb(220, 50, 47), // #dc322f (red)
|
||||||
info: Color::Rgb(133, 153, 0),
|
info: Color::Rgb(133, 153, 0),
|
||||||
@@ -645,6 +735,12 @@ fn midnight_ocean() -> Theme {
|
|||||||
selection_bg: Color::Rgb(56, 139, 253),
|
selection_bg: Color::Rgb(56, 139, 253),
|
||||||
selection_fg: Color::Rgb(13, 17, 23),
|
selection_fg: Color::Rgb(13, 17, 23),
|
||||||
cursor: Color::Rgb(246, 140, 245),
|
cursor: Color::Rgb(246, 140, 245),
|
||||||
|
code_block_background: Color::Rgb(22, 27, 34),
|
||||||
|
code_block_border: Color::Rgb(88, 166, 255),
|
||||||
|
code_block_text: Color::Rgb(192, 202, 245),
|
||||||
|
code_block_keyword: Color::Rgb(255, 212, 59),
|
||||||
|
code_block_string: Color::Rgb(158, 206, 106),
|
||||||
|
code_block_comment: Color::Rgb(110, 118, 129),
|
||||||
placeholder: Color::Rgb(110, 118, 129),
|
placeholder: Color::Rgb(110, 118, 129),
|
||||||
error: Color::Rgb(248, 81, 73),
|
error: Color::Rgb(248, 81, 73),
|
||||||
info: Color::Rgb(158, 206, 106),
|
info: Color::Rgb(158, 206, 106),
|
||||||
@@ -688,6 +784,12 @@ fn rose_pine() -> Theme {
|
|||||||
selection_bg: Color::Rgb(64, 61, 82),
|
selection_bg: Color::Rgb(64, 61, 82),
|
||||||
selection_fg: Color::Rgb(224, 222, 244),
|
selection_fg: Color::Rgb(224, 222, 244),
|
||||||
cursor: Color::Rgb(235, 111, 146),
|
cursor: Color::Rgb(235, 111, 146),
|
||||||
|
code_block_background: Color::Rgb(38, 35, 58),
|
||||||
|
code_block_border: Color::Rgb(235, 111, 146),
|
||||||
|
code_block_text: Color::Rgb(224, 222, 244),
|
||||||
|
code_block_keyword: Color::Rgb(246, 193, 119),
|
||||||
|
code_block_string: Color::Rgb(156, 207, 216),
|
||||||
|
code_block_comment: Color::Rgb(110, 106, 134),
|
||||||
placeholder: Color::Rgb(110, 106, 134),
|
placeholder: Color::Rgb(110, 106, 134),
|
||||||
error: Color::Rgb(235, 111, 146),
|
error: Color::Rgb(235, 111, 146),
|
||||||
info: Color::Rgb(156, 207, 216),
|
info: Color::Rgb(156, 207, 216),
|
||||||
@@ -731,6 +833,12 @@ fn monokai() -> Theme {
|
|||||||
selection_bg: Color::Rgb(117, 113, 94),
|
selection_bg: Color::Rgb(117, 113, 94),
|
||||||
selection_fg: Color::Rgb(248, 248, 242),
|
selection_fg: Color::Rgb(248, 248, 242),
|
||||||
cursor: Color::Rgb(249, 38, 114),
|
cursor: Color::Rgb(249, 38, 114),
|
||||||
|
code_block_background: Color::Rgb(50, 51, 46),
|
||||||
|
code_block_border: Color::Rgb(249, 38, 114),
|
||||||
|
code_block_text: Color::Rgb(248, 248, 242),
|
||||||
|
code_block_keyword: Color::Rgb(230, 219, 116),
|
||||||
|
code_block_string: Color::Rgb(166, 226, 46),
|
||||||
|
code_block_comment: Color::Rgb(117, 113, 94),
|
||||||
placeholder: Color::Rgb(117, 113, 94),
|
placeholder: Color::Rgb(117, 113, 94),
|
||||||
error: Color::Rgb(249, 38, 114),
|
error: Color::Rgb(249, 38, 114),
|
||||||
info: Color::Rgb(166, 226, 46),
|
info: Color::Rgb(166, 226, 46),
|
||||||
@@ -774,6 +882,12 @@ fn material_dark() -> Theme {
|
|||||||
selection_bg: Color::Rgb(84, 110, 122),
|
selection_bg: Color::Rgb(84, 110, 122),
|
||||||
selection_fg: Color::Rgb(238, 255, 255),
|
selection_fg: Color::Rgb(238, 255, 255),
|
||||||
cursor: Color::Rgb(255, 204, 0),
|
cursor: Color::Rgb(255, 204, 0),
|
||||||
|
code_block_background: Color::Rgb(33, 43, 48),
|
||||||
|
code_block_border: Color::Rgb(128, 203, 196),
|
||||||
|
code_block_text: Color::Rgb(238, 255, 255),
|
||||||
|
code_block_keyword: Color::Rgb(255, 203, 107),
|
||||||
|
code_block_string: Color::Rgb(195, 232, 141),
|
||||||
|
code_block_comment: Color::Rgb(84, 110, 122),
|
||||||
placeholder: Color::Rgb(84, 110, 122),
|
placeholder: Color::Rgb(84, 110, 122),
|
||||||
error: Color::Rgb(240, 113, 120),
|
error: Color::Rgb(240, 113, 120),
|
||||||
info: Color::Rgb(195, 232, 141),
|
info: Color::Rgb(195, 232, 141),
|
||||||
@@ -817,6 +931,12 @@ fn material_light() -> Theme {
|
|||||||
selection_bg: Color::Rgb(176, 190, 197),
|
selection_bg: Color::Rgb(176, 190, 197),
|
||||||
selection_fg: Color::Rgb(33, 33, 33),
|
selection_fg: Color::Rgb(33, 33, 33),
|
||||||
cursor: Color::Rgb(194, 24, 91),
|
cursor: Color::Rgb(194, 24, 91),
|
||||||
|
code_block_background: Color::Rgb(248, 249, 250),
|
||||||
|
code_block_border: Color::Rgb(0, 150, 136),
|
||||||
|
code_block_text: Color::Rgb(33, 33, 33),
|
||||||
|
code_block_keyword: Color::Rgb(245, 124, 0),
|
||||||
|
code_block_string: Color::Rgb(56, 142, 60),
|
||||||
|
code_block_comment: Color::Rgb(144, 164, 174),
|
||||||
placeholder: Color::Rgb(144, 164, 174),
|
placeholder: Color::Rgb(144, 164, 174),
|
||||||
error: Color::Rgb(211, 47, 47),
|
error: Color::Rgb(211, 47, 47),
|
||||||
info: Color::Rgb(56, 142, 60),
|
info: Color::Rgb(56, 142, 60),
|
||||||
@@ -860,6 +980,12 @@ fn grayscale_high_contrast() -> Theme {
|
|||||||
selection_bg: Color::Rgb(240, 240, 240),
|
selection_bg: Color::Rgb(240, 240, 240),
|
||||||
selection_fg: Color::Black,
|
selection_fg: Color::Black,
|
||||||
cursor: Color::White,
|
cursor: Color::White,
|
||||||
|
code_block_background: Color::Rgb(15, 15, 15),
|
||||||
|
code_block_border: Color::White,
|
||||||
|
code_block_text: Color::Rgb(247, 247, 247),
|
||||||
|
code_block_keyword: Color::Rgb(204, 204, 204),
|
||||||
|
code_block_string: Color::Rgb(214, 214, 214),
|
||||||
|
code_block_comment: Color::Rgb(122, 122, 122),
|
||||||
placeholder: Color::Rgb(122, 122, 122),
|
placeholder: Color::Rgb(122, 122, 122),
|
||||||
error: Color::White,
|
error: Color::White,
|
||||||
info: Color::Rgb(200, 200, 200),
|
info: Color::Rgb(200, 200, 200),
|
||||||
|
|||||||
@@ -18,6 +18,17 @@ pub use crate::state::AutoScroll;
|
|||||||
/// Visual selection state for text selection
|
/// Visual selection state for text selection
|
||||||
pub use crate::state::VisualSelection;
|
pub use crate::state::VisualSelection;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// How role labels should be rendered alongside chat messages.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum RoleLabelDisplay {
|
||||||
|
Inline,
|
||||||
|
Above,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract text from a selection range in a list of lines
|
/// Extract text from a selection range in a list of lines
|
||||||
pub fn extract_text_from_selection(
|
pub fn extract_text_from_selection(
|
||||||
lines: &[String],
|
lines: &[String],
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ use crate::chat_app::{ChatApp, HELP_TAB_COUNT, MessageRenderContext, ModelSelect
|
|||||||
use owlen_core::model::DetailedModelInfo;
|
use owlen_core::model::DetailedModelInfo;
|
||||||
use owlen_core::theme::Theme;
|
use owlen_core::theme::Theme;
|
||||||
use owlen_core::types::{ModelInfo, Role};
|
use owlen_core::types::{ModelInfo, Role};
|
||||||
use owlen_core::ui::{FocusedPanel, InputMode};
|
use owlen_core::ui::{FocusedPanel, InputMode, RoleLabelDisplay};
|
||||||
|
|
||||||
const PRIVACY_TAB_INDEX: usize = HELP_TAB_COUNT - 1;
|
const PRIVACY_TAB_INDEX: usize = HELP_TAB_COUNT - 1;
|
||||||
|
|
||||||
@@ -687,7 +687,7 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) {
|
|||||||
|
|
||||||
// Build the lines for messages using cached rendering
|
// Build the lines for messages using cached rendering
|
||||||
let mut lines: Vec<Line<'static>> = Vec::new();
|
let mut lines: Vec<Line<'static>> = Vec::new();
|
||||||
let show_role_labels = formatter.show_role_labels();
|
let role_label_mode = formatter.role_label_mode();
|
||||||
for message_index in 0..total_messages {
|
for message_index in 0..total_messages {
|
||||||
let is_streaming = {
|
let is_streaming = {
|
||||||
let conversation = app.conversation();
|
let conversation = app.conversation();
|
||||||
@@ -701,12 +701,13 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) {
|
|||||||
message_index,
|
message_index,
|
||||||
MessageRenderContext::new(
|
MessageRenderContext::new(
|
||||||
&mut formatter,
|
&mut formatter,
|
||||||
show_role_labels,
|
role_label_mode,
|
||||||
content_width as usize,
|
content_width as usize,
|
||||||
message_index + 1 == total_messages,
|
message_index + 1 == total_messages,
|
||||||
is_streaming,
|
is_streaming,
|
||||||
app.get_loading_indicator(),
|
app.get_loading_indicator(),
|
||||||
&theme,
|
&theme,
|
||||||
|
app.should_highlight_code(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
lines.extend(message_lines);
|
lines.extend(message_lines);
|
||||||
@@ -729,6 +730,37 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if app.get_loading_indicator() != "" && last_message_is_user {
|
if app.get_loading_indicator() != "" && last_message_is_user {
|
||||||
|
match role_label_mode {
|
||||||
|
RoleLabelDisplay::Inline => {
|
||||||
|
let (emoji, title) = crate::chat_app::role_label_parts(&Role::Assistant);
|
||||||
|
let inline_label = format!("{emoji} {title}:");
|
||||||
|
let label_width = UnicodeWidthStr::width(inline_label.as_str());
|
||||||
|
let max_label_width = crate::chat_app::max_inline_label_width();
|
||||||
|
let padding = max_label_width.saturating_sub(label_width);
|
||||||
|
|
||||||
|
let mut loading_spans = vec![
|
||||||
|
Span::raw(format!("{emoji} ")),
|
||||||
|
Span::styled(
|
||||||
|
format!("{title}:"),
|
||||||
|
Style::default()
|
||||||
|
.fg(theme.assistant_message_role)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
if padding > 0 {
|
||||||
|
loading_spans.push(Span::raw(" ".repeat(padding)));
|
||||||
|
}
|
||||||
|
|
||||||
|
loading_spans.push(Span::raw(" "));
|
||||||
|
loading_spans.push(Span::styled(
|
||||||
|
app.get_loading_indicator().to_string(),
|
||||||
|
Style::default().fg(theme.info),
|
||||||
|
));
|
||||||
|
|
||||||
|
lines.push(Line::from(loading_spans));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
let loading_spans = vec![
|
let loading_spans = vec![
|
||||||
Span::raw("🤖 "),
|
Span::raw("🤖 "),
|
||||||
Span::styled(
|
Span::styled(
|
||||||
@@ -744,6 +776,8 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) {
|
|||||||
];
|
];
|
||||||
lines.push(Line::from(loading_spans));
|
lines.push(Line::from(loading_spans));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if lines.is_empty() {
|
if lines.is_empty() {
|
||||||
lines.push(Line::from("No messages yet. Press 'i' to start typing."));
|
lines.push(Line::from("No messages yet. Press 'i' to start typing."));
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ These settings customize the look and feel of the terminal interface.
|
|||||||
- `max_history_lines` (integer, default: `2000`)
|
- `max_history_lines` (integer, default: `2000`)
|
||||||
The maximum number of lines to keep in the scrollback buffer for the chat history.
|
The maximum number of lines to keep in the scrollback buffer for the chat history.
|
||||||
|
|
||||||
- `show_role_labels` (boolean, default: `true`)
|
- `role_label` (string, default: `"above"`)
|
||||||
Whether to show the `user` and `bot` role labels next to messages.
|
Controls how sender labels are rendered next to messages. Valid values are `"above"` (label on its own line), `"inline"` (label shares the first line of the message), and `"none"` (no label).
|
||||||
|
|
||||||
- `wrap_column` (integer, default: `100`)
|
- `wrap_column` (integer, default: `100`)
|
||||||
The column at which to wrap text if `word_wrap` is enabled.
|
The column at which to wrap text if `word_wrap` is enabled.
|
||||||
@@ -70,6 +70,9 @@ These settings customize the look and feel of the terminal interface.
|
|||||||
- `scrollback_lines` (integer, default: `2000`)
|
- `scrollback_lines` (integer, default: `2000`)
|
||||||
The maximum number of rendered lines the chat view keeps in memory. Set to `0` to disable trimming entirely if you prefer unlimited history.
|
The maximum number of rendered lines the chat view keeps in memory. Set to `0` to disable trimming entirely if you prefer unlimited history.
|
||||||
|
|
||||||
|
- `syntax_highlighting` (boolean, default: `false`)
|
||||||
|
Enables lightweight syntax highlighting inside fenced code blocks when the terminal supports 256-color output.
|
||||||
|
|
||||||
## Storage Settings (`[storage]`)
|
## Storage Settings (`[storage]`)
|
||||||
|
|
||||||
These settings control how conversations are saved and loaded.
|
These settings control how conversations are saved and loaded.
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ selection_bg = "#0000ff" # Selection background
|
|||||||
selection_fg = "#ffffff" # Selection foreground
|
selection_fg = "#ffffff" # Selection foreground
|
||||||
cursor = "#ff0080" # Cursor color
|
cursor = "#ff0080" # Cursor color
|
||||||
|
|
||||||
|
# Code block styling
|
||||||
|
code_block_background = "#111111"
|
||||||
|
code_block_border = "#ff00ff"
|
||||||
|
code_block_text = "#ffffff"
|
||||||
|
code_block_keyword = "#ffff00"
|
||||||
|
code_block_string = "#00ff00"
|
||||||
|
code_block_comment = "#808080"
|
||||||
|
|
||||||
# Status colors
|
# Status colors
|
||||||
error = "#ff0000" # Error messages
|
error = "#ff0000" # Error messages
|
||||||
info = "#00ff00" # Info/success messages
|
info = "#00ff00" # Info/success messages
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ mode_command = "yellow"
|
|||||||
selection_bg = "blue"
|
selection_bg = "blue"
|
||||||
selection_fg = "white"
|
selection_fg = "white"
|
||||||
cursor = "white"
|
cursor = "white"
|
||||||
|
code_block_background = "black"
|
||||||
|
code_block_border = "cyan"
|
||||||
|
code_block_text = "white"
|
||||||
|
code_block_keyword = "yellow"
|
||||||
|
code_block_string = "green"
|
||||||
|
code_block_comment = "darkgray"
|
||||||
placeholder = "darkgray"
|
placeholder = "darkgray"
|
||||||
error = "red"
|
error = "red"
|
||||||
info = "green"
|
info = "green"
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ mode_command = "yellow"
|
|||||||
selection_bg = "lightblue"
|
selection_bg = "lightblue"
|
||||||
selection_fg = "black"
|
selection_fg = "black"
|
||||||
cursor = "magenta"
|
cursor = "magenta"
|
||||||
|
code_block_background = "#191919"
|
||||||
|
code_block_border = "lightmagenta"
|
||||||
|
code_block_text = "white"
|
||||||
|
code_block_keyword = "yellow"
|
||||||
|
code_block_string = "lightgreen"
|
||||||
|
code_block_comment = "gray"
|
||||||
placeholder = "darkgray"
|
placeholder = "darkgray"
|
||||||
error = "red"
|
error = "red"
|
||||||
info = "lightgreen"
|
info = "lightgreen"
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ mode_command = "#b58900"
|
|||||||
selection_bg = "#a4c8f0"
|
selection_bg = "#a4c8f0"
|
||||||
selection_fg = "black"
|
selection_fg = "black"
|
||||||
cursor = "#d95f02"
|
cursor = "#d95f02"
|
||||||
|
code_block_background = "#f5f5f5"
|
||||||
|
code_block_border = "#009688"
|
||||||
|
code_block_text = "black"
|
||||||
|
code_block_keyword = "#b58900"
|
||||||
|
code_block_string = "#388e3c"
|
||||||
|
code_block_comment = "#90a4ae"
|
||||||
placeholder = "gray"
|
placeholder = "gray"
|
||||||
error = "#c0392b"
|
error = "#c0392b"
|
||||||
info = "green"
|
info = "green"
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ mode_command = "#f1fa8c"
|
|||||||
selection_bg = "#44475a"
|
selection_bg = "#44475a"
|
||||||
selection_fg = "#f8f8f2"
|
selection_fg = "#f8f8f2"
|
||||||
cursor = "#ff79c6"
|
cursor = "#ff79c6"
|
||||||
|
code_block_background = "#44475a"
|
||||||
|
code_block_border = "#bd93f9"
|
||||||
|
code_block_text = "#f8f8f2"
|
||||||
|
code_block_keyword = "#ff79c6"
|
||||||
|
code_block_string = "#50fa7b"
|
||||||
|
code_block_comment = "#6272a4"
|
||||||
placeholder = "#6272a4"
|
placeholder = "#6272a4"
|
||||||
error = "#ff5555"
|
error = "#ff5555"
|
||||||
info = "#50fa7b"
|
info = "#50fa7b"
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ mode_command = "#d0d0d0"
|
|||||||
selection_bg = "#f0f0f0"
|
selection_bg = "#f0f0f0"
|
||||||
selection_fg = "#000000"
|
selection_fg = "#000000"
|
||||||
cursor = "#ffffff"
|
cursor = "#ffffff"
|
||||||
|
code_block_background = "#0f0f0f"
|
||||||
|
code_block_border = "#ffffff"
|
||||||
|
code_block_text = "#f7f7f7"
|
||||||
|
code_block_keyword = "#cccccc"
|
||||||
|
code_block_string = "#d6d6d6"
|
||||||
|
code_block_comment = "#7a7a7a"
|
||||||
placeholder = "#7a7a7a"
|
placeholder = "#7a7a7a"
|
||||||
error = "#ffffff"
|
error = "#ffffff"
|
||||||
info = "#c8c8c8"
|
info = "#c8c8c8"
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ mode_command = "#fabd2f"
|
|||||||
selection_bg = "#504945"
|
selection_bg = "#504945"
|
||||||
selection_fg = "#ebdbb2"
|
selection_fg = "#ebdbb2"
|
||||||
cursor = "#fe8019"
|
cursor = "#fe8019"
|
||||||
|
code_block_background = "#3c3836"
|
||||||
|
code_block_border = "#7c6f64"
|
||||||
|
code_block_text = "#ebdbb2"
|
||||||
|
code_block_keyword = "#fabd2f"
|
||||||
|
code_block_string = "#8ec07c"
|
||||||
|
code_block_comment = "#7c6f64"
|
||||||
placeholder = "#665c54"
|
placeholder = "#665c54"
|
||||||
error = "#fb4934"
|
error = "#fb4934"
|
||||||
info = "#b8bb26"
|
info = "#b8bb26"
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ mode_command = "#ffcb6b"
|
|||||||
selection_bg = "#546e7a"
|
selection_bg = "#546e7a"
|
||||||
selection_fg = "#eeffff"
|
selection_fg = "#eeffff"
|
||||||
cursor = "#ffcc00"
|
cursor = "#ffcc00"
|
||||||
|
code_block_background = "#212b30"
|
||||||
|
code_block_border = "#80cbc4"
|
||||||
|
code_block_text = "#eeffff"
|
||||||
|
code_block_keyword = "#ffcb6b"
|
||||||
|
code_block_string = "#c3e88d"
|
||||||
|
code_block_comment = "#546e7a"
|
||||||
placeholder = "#546e7a"
|
placeholder = "#546e7a"
|
||||||
error = "#f07178"
|
error = "#f07178"
|
||||||
info = "#c3e88d"
|
info = "#c3e88d"
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ mode_command = "#f57c00"
|
|||||||
selection_bg = "#b0bec5"
|
selection_bg = "#b0bec5"
|
||||||
selection_fg = "#212121"
|
selection_fg = "#212121"
|
||||||
cursor = "#c2185b"
|
cursor = "#c2185b"
|
||||||
|
code_block_background = "#f8f9fa"
|
||||||
|
code_block_border = "#009688"
|
||||||
|
code_block_text = "#212121"
|
||||||
|
code_block_keyword = "#f57c00"
|
||||||
|
code_block_string = "#388e3c"
|
||||||
|
code_block_comment = "#90a4ae"
|
||||||
placeholder = "#90a4ae"
|
placeholder = "#90a4ae"
|
||||||
error = "#d32f2f"
|
error = "#d32f2f"
|
||||||
info = "#388e3c"
|
info = "#388e3c"
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ mode_command = "#ffd43b"
|
|||||||
selection_bg = "#388bfd"
|
selection_bg = "#388bfd"
|
||||||
selection_fg = "#0d1117"
|
selection_fg = "#0d1117"
|
||||||
cursor = "#f68cf5"
|
cursor = "#f68cf5"
|
||||||
|
code_block_background = "#161b22"
|
||||||
|
code_block_border = "#58a6ff"
|
||||||
|
code_block_text = "#c0caf5"
|
||||||
|
code_block_keyword = "#ffd43b"
|
||||||
|
code_block_string = "#9ece6a"
|
||||||
|
code_block_comment = "#6e7681"
|
||||||
placeholder = "#6e7681"
|
placeholder = "#6e7681"
|
||||||
error = "#f85149"
|
error = "#f85149"
|
||||||
info = "#9ece6a"
|
info = "#9ece6a"
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ mode_command = "#e6db74"
|
|||||||
selection_bg = "#75715e"
|
selection_bg = "#75715e"
|
||||||
selection_fg = "#f8f8f2"
|
selection_fg = "#f8f8f2"
|
||||||
cursor = "#f92672"
|
cursor = "#f92672"
|
||||||
|
code_block_background = "#32332e"
|
||||||
|
code_block_border = "#f92672"
|
||||||
|
code_block_text = "#f8f8f2"
|
||||||
|
code_block_keyword = "#e6db74"
|
||||||
|
code_block_string = "#a6e22e"
|
||||||
|
code_block_comment = "#75715e"
|
||||||
placeholder = "#75715e"
|
placeholder = "#75715e"
|
||||||
error = "#f92672"
|
error = "#f92672"
|
||||||
info = "#a6e22e"
|
info = "#a6e22e"
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ mode_command = "#f6c177"
|
|||||||
selection_bg = "#403d52"
|
selection_bg = "#403d52"
|
||||||
selection_fg = "#e0def4"
|
selection_fg = "#e0def4"
|
||||||
cursor = "#eb6f92"
|
cursor = "#eb6f92"
|
||||||
|
code_block_background = "#26233a"
|
||||||
|
code_block_border = "#eb6f92"
|
||||||
|
code_block_text = "#e0def4"
|
||||||
|
code_block_keyword = "#f6c177"
|
||||||
|
code_block_string = "#9ccfd8"
|
||||||
|
code_block_comment = "#6e6a86"
|
||||||
placeholder = "#6e6a86"
|
placeholder = "#6e6a86"
|
||||||
error = "#eb6f92"
|
error = "#eb6f92"
|
||||||
info = "#9ccfd8"
|
info = "#9ccfd8"
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ mode_command = "#b58900"
|
|||||||
selection_bg = "#073642"
|
selection_bg = "#073642"
|
||||||
selection_fg = "#93a1a1"
|
selection_fg = "#93a1a1"
|
||||||
cursor = "#d33682"
|
cursor = "#d33682"
|
||||||
|
code_block_background = "#073642"
|
||||||
|
code_block_border = "#268bd2"
|
||||||
|
code_block_text = "#93a1a1"
|
||||||
|
code_block_keyword = "#b58900"
|
||||||
|
code_block_string = "#859900"
|
||||||
|
code_block_comment = "#586e75"
|
||||||
placeholder = "#586e75"
|
placeholder = "#586e75"
|
||||||
error = "#dc322f"
|
error = "#dc322f"
|
||||||
info = "#859900"
|
info = "#859900"
|
||||||
|
|||||||
Reference in New Issue
Block a user