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:
2025-10-12 16:44:53 +02:00
parent ae9c3af096
commit 55e6b0583d
22 changed files with 1484 additions and 140 deletions

View File

@@ -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() {
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")
);
unsafe {
std::env::remove_var("OWLEN_TEST_API_KEY");
}
}
#[test]
fn expand_provider_env_vars_errors_for_missing_variable() {
unsafe {
std::env::remove_var("OWLEN_TEST_MISSING");
}
let mut config = Config::default();
if let Some(ollama) = config.providers.get_mut("ollama") {

View File

@@ -1,19 +1,20 @@
use crate::types::Message;
use crate::ui::RoleLabelDisplay;
/// Formats messages for display across different clients.
#[derive(Debug, Clone)]
pub struct MessageFormatter {
wrap_width: usize,
show_role_labels: bool,
role_label_mode: RoleLabelDisplay,
preserve_empty_lines: bool,
}
impl MessageFormatter {
/// 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 {
wrap_width: wrap_width.max(20),
show_role_labels,
role_label_mode,
preserve_empty_lines: false,
}
}
@@ -29,9 +30,19 @@ impl MessageFormatter {
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 {
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> {

View File

@@ -1031,13 +1031,17 @@ mod tests {
#[test]
fn resolve_api_key_expands_env_var() {
unsafe {
std::env::set_var("OLLAMA_TEST_KEY", "secret");
}
assert_eq!(
resolve_api_key(Some("${OLLAMA_TEST_KEY}".into())),
Some("secret".into())
);
unsafe {
std::env::remove_var("OLLAMA_TEST_KEY");
}
}
#[test]
fn normalize_base_url_removes_api_path() {

View File

@@ -16,7 +16,7 @@ use crate::storage::{SessionMeta, StorageManager};
use crate::types::{
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::{ChatStream, Provider};
use crate::{
@@ -264,7 +264,7 @@ impl SessionController {
);
let formatter = MessageFormatter::new(
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);
let input_buffer = InputBuffer::new(
@@ -351,6 +351,10 @@ impl SessionController {
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).
pub async fn config_async(&self) -> tokio::sync::MutexGuard<'_, Config> {
self.config.lock().await

View File

@@ -116,6 +116,42 @@ pub struct Theme {
#[serde(serialize_with = "serialize_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
#[serde(deserialize_with = "deserialize_color")]
#[serde(serialize_with = "serialize_color")]
@@ -217,6 +253,30 @@ impl Default for 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 {
Color::LightBlue
}
@@ -430,6 +490,12 @@ fn default_dark() -> Theme {
selection_bg: Color::LightBlue,
selection_fg: Color::Black,
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,
error: Color::Red,
info: Color::LightGreen,
@@ -473,6 +539,12 @@ fn default_light() -> Theme {
selection_bg: Color::Rgb(164, 200, 240),
selection_fg: Color::Black,
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,
error: Color::Rgb(192, 57, 43),
info: Color::Green,
@@ -516,6 +588,12 @@ fn gruvbox() -> Theme {
selection_bg: Color::Rgb(80, 73, 69),
selection_fg: Color::Rgb(235, 219, 178),
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),
error: Color::Rgb(251, 73, 52), // #fb4934
info: Color::Rgb(184, 187, 38),
@@ -559,6 +637,12 @@ fn dracula() -> Theme {
selection_bg: Color::Rgb(68, 71, 90),
selection_fg: Color::Rgb(248, 248, 242),
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),
error: Color::Rgb(255, 85, 85), // #ff5555
info: Color::Rgb(80, 250, 123),
@@ -602,6 +686,12 @@ fn solarized() -> Theme {
selection_bg: Color::Rgb(7, 54, 66),
selection_fg: Color::Rgb(147, 161, 161),
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),
error: Color::Rgb(220, 50, 47), // #dc322f (red)
info: Color::Rgb(133, 153, 0),
@@ -645,6 +735,12 @@ fn midnight_ocean() -> Theme {
selection_bg: Color::Rgb(56, 139, 253),
selection_fg: Color::Rgb(13, 17, 23),
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),
error: Color::Rgb(248, 81, 73),
info: Color::Rgb(158, 206, 106),
@@ -688,6 +784,12 @@ fn rose_pine() -> Theme {
selection_bg: Color::Rgb(64, 61, 82),
selection_fg: Color::Rgb(224, 222, 244),
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),
error: Color::Rgb(235, 111, 146),
info: Color::Rgb(156, 207, 216),
@@ -731,6 +833,12 @@ fn monokai() -> Theme {
selection_bg: Color::Rgb(117, 113, 94),
selection_fg: Color::Rgb(248, 248, 242),
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),
error: Color::Rgb(249, 38, 114),
info: Color::Rgb(166, 226, 46),
@@ -774,6 +882,12 @@ fn material_dark() -> Theme {
selection_bg: Color::Rgb(84, 110, 122),
selection_fg: Color::Rgb(238, 255, 255),
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),
error: Color::Rgb(240, 113, 120),
info: Color::Rgb(195, 232, 141),
@@ -817,6 +931,12 @@ fn material_light() -> Theme {
selection_bg: Color::Rgb(176, 190, 197),
selection_fg: Color::Rgb(33, 33, 33),
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),
error: Color::Rgb(211, 47, 47),
info: Color::Rgb(56, 142, 60),
@@ -860,6 +980,12 @@ fn grayscale_high_contrast() -> Theme {
selection_bg: Color::Rgb(240, 240, 240),
selection_fg: Color::Black,
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),
error: Color::White,
info: Color::Rgb(200, 200, 200),

View File

@@ -18,6 +18,17 @@ pub use crate::state::AutoScroll;
/// Visual selection state for text selection
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
pub fn extract_text_from_selection(
lines: &[String],

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@ use crate::chat_app::{ChatApp, HELP_TAB_COUNT, MessageRenderContext, ModelSelect
use owlen_core::model::DetailedModelInfo;
use owlen_core::theme::Theme;
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;
@@ -687,7 +687,7 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) {
// Build the lines for messages using cached rendering
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 {
let is_streaming = {
let conversation = app.conversation();
@@ -701,12 +701,13 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) {
message_index,
MessageRenderContext::new(
&mut formatter,
show_role_labels,
role_label_mode,
content_width as usize,
message_index + 1 == total_messages,
is_streaming,
app.get_loading_indicator(),
&theme,
app.should_highlight_code(),
),
);
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 {
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![
Span::raw("🤖 "),
Span::styled(
@@ -744,6 +776,8 @@ fn render_messages(frame: &mut Frame<'_>, area: Rect, app: &mut ChatApp) {
];
lines.push(Line::from(loading_spans));
}
}
}
if lines.is_empty() {
lines.push(Line::from("No messages yet. Press 'i' to start typing."));

View File

@@ -58,8 +58,8 @@ These settings customize the look and feel of the terminal interface.
- `max_history_lines` (integer, default: `2000`)
The maximum number of lines to keep in the scrollback buffer for the chat history.
- `show_role_labels` (boolean, default: `true`)
Whether to show the `user` and `bot` role labels next to messages.
- `role_label` (string, default: `"above"`)
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`)
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`)
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]`)
These settings control how conversations are saved and loaded.

View File

@@ -55,6 +55,14 @@ selection_bg = "#0000ff" # Selection background
selection_fg = "#ffffff" # Selection foreground
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
error = "#ff0000" # Error messages
info = "#00ff00" # Info/success messages

View File

@@ -19,6 +19,12 @@ mode_command = "yellow"
selection_bg = "blue"
selection_fg = "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"
error = "red"
info = "green"

View File

@@ -19,6 +19,12 @@ mode_command = "yellow"
selection_bg = "lightblue"
selection_fg = "black"
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"
error = "red"
info = "lightgreen"

View File

@@ -19,6 +19,12 @@ mode_command = "#b58900"
selection_bg = "#a4c8f0"
selection_fg = "black"
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"
error = "#c0392b"
info = "green"

View File

@@ -19,6 +19,12 @@ mode_command = "#f1fa8c"
selection_bg = "#44475a"
selection_fg = "#f8f8f2"
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"
error = "#ff5555"
info = "#50fa7b"

View File

@@ -19,6 +19,12 @@ mode_command = "#d0d0d0"
selection_bg = "#f0f0f0"
selection_fg = "#000000"
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"
error = "#ffffff"
info = "#c8c8c8"

View File

@@ -19,6 +19,12 @@ mode_command = "#fabd2f"
selection_bg = "#504945"
selection_fg = "#ebdbb2"
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"
error = "#fb4934"
info = "#b8bb26"

View File

@@ -19,6 +19,12 @@ mode_command = "#ffcb6b"
selection_bg = "#546e7a"
selection_fg = "#eeffff"
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"
error = "#f07178"
info = "#c3e88d"

View File

@@ -19,6 +19,12 @@ mode_command = "#f57c00"
selection_bg = "#b0bec5"
selection_fg = "#212121"
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"
error = "#d32f2f"
info = "#388e3c"

View File

@@ -19,6 +19,12 @@ mode_command = "#ffd43b"
selection_bg = "#388bfd"
selection_fg = "#0d1117"
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"
error = "#f85149"
info = "#9ece6a"

View File

@@ -19,6 +19,12 @@ mode_command = "#e6db74"
selection_bg = "#75715e"
selection_fg = "#f8f8f2"
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"
error = "#f92672"
info = "#a6e22e"

View File

@@ -19,6 +19,12 @@ mode_command = "#f6c177"
selection_bg = "#403d52"
selection_fg = "#e0def4"
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"
error = "#eb6f92"
info = "#9ccfd8"

View File

@@ -19,6 +19,12 @@ mode_command = "#b58900"
selection_bg = "#073642"
selection_fg = "#93a1a1"
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"
error = "#dc322f"
info = "#859900"