Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/tui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub(crate) mod search_state;
pub(crate) mod search_worker;
pub(crate) mod share_state;
pub(crate) mod text_layout;
pub(crate) mod theme;
pub(crate) mod ui;
pub(crate) mod usage_state;
pub(crate) mod viewing_state;
78 changes: 78 additions & 0 deletions src/tui/theme.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use ratatui::style::Color;

pub(crate) struct Theme {
pub(crate) text: Color,
pub(crate) text_muted: Color,
pub(crate) accent: Color,
pub(crate) highlight: Color,
pub(crate) success: Color,
pub(crate) error: Color,
pub(crate) info: Color,

pub(crate) border_focus: Color,
pub(crate) border_idle: Color,
pub(crate) background: Color,
pub(crate) popup_bg: Color,

pub(crate) selected_fg: Color,
pub(crate) selected_bg: Color,
// Dimmer band behind the selected message in the preview/viewing panes; distinct
// from the strong list/filter row selection above.
pub(crate) message_highlight: Color,

pub(crate) match_fg: Color,
pub(crate) match_bg: Color,

pub(crate) source: Color,
pub(crate) user: Color,
pub(crate) assistant: Color,
pub(crate) summary: Color,

pub(crate) scrollbar_thumb: Color,
pub(crate) scrollbar_track: Color,

// Usage dashboard: skill-audit accent and the categorical token-mix series
pub(crate) skill: Color,
pub(crate) token_input: Color,
pub(crate) token_output: Color,
pub(crate) token_cache_read: Color,
pub(crate) token_cache_write: Color,
pub(crate) token_reasoning: Color,
}

pub(crate) const THEME: Theme = Theme {
text: Color::White,
text_muted: Color::DarkGray,
accent: Color::Yellow,
highlight: Color::Cyan,
success: Color::Green,
error: Color::Red,
info: Color::Blue,

border_focus: Color::Cyan,
border_idle: Color::DarkGray,
background: Color::Reset,
popup_bg: Color::Black,

selected_fg: Color::Black,
selected_bg: Color::Cyan,
message_highlight: Color::DarkGray,

match_fg: Color::Black,
match_bg: Color::Yellow,

source: Color::Green,
user: Color::Cyan,
assistant: Color::Green,
summary: Color::Green,

scrollbar_thumb: Color::Cyan,
scrollbar_track: Color::DarkGray,

skill: Color::Magenta,
token_input: Color::Cyan,
token_output: Color::Green,
token_cache_read: Color::Blue,
token_cache_write: Color::Magenta,
token_reasoning: Color::Yellow,
};
23 changes: 12 additions & 11 deletions src/tui/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use ratatui::widgets::{Scrollbar, ScrollbarOrientation, ScrollbarState};

use crate::tui::app::App;
use crate::tui::share_state::{AppMode, ResumeOrigin};
use crate::tui::theme::THEME;

pub(super) fn highlight_spans(
text: &str,
Expand All @@ -28,7 +29,7 @@ pub(super) fn highlight_spans(
let mut spans: Vec<Span<'static>> = Vec::new();
let mut cursor = 0usize;
let match_style =
Style::default().fg(Color::Black).bg(Color::Yellow).add_modifier(Modifier::BOLD);
Style::default().fg(THEME.match_fg).bg(THEME.match_bg).add_modifier(Modifier::BOLD);
while cursor < text.len() {
let hit = needles
.iter()
Expand Down Expand Up @@ -99,8 +100,8 @@ pub(super) fn render_vertical_scrollbar(
.end_symbol(None)
.thumb_symbol("▌")
.track_symbol(Some("▌"))
.thumb_style(Style::default().fg(Color::Cyan))
.track_style(Style::default().fg(Color::DarkGray)),
.thumb_style(Style::default().fg(THEME.scrollbar_thumb))
.track_style(Style::default().fg(THEME.scrollbar_track)),
area.inner(Margin { vertical: 1, horizontal: 0 }),
&mut state,
);
Expand Down Expand Up @@ -239,7 +240,7 @@ mod tests {
})
.unwrap();

assert_eq!(frame.buffer[(1, 8)].style().fg, Some(Color::Cyan));
assert_eq!(frame.buffer[(1, 8)].style().fg, Some(THEME.scrollbar_thumb));
}

#[test]
Expand All @@ -253,9 +254,9 @@ mod tests {

let texts: Vec<&str> = spans.iter().map(|s| s.content.as_ref()).collect();
assert_eq!(texts, vec!["Alpha", " beta ", "Gamma"]);
assert_eq!(spans[0].style.bg, Some(Color::Yellow));
assert_eq!(spans[0].style.bg, Some(THEME.match_bg));
assert_eq!(spans[1].style.bg, None);
assert_eq!(spans[2].style.bg, Some(Color::Yellow));
assert_eq!(spans[2].style.bg, Some(THEME.match_bg));
}

#[test]
Expand All @@ -269,7 +270,7 @@ mod tests {

let texts: Vec<&str> = spans.iter().map(|s| s.content.as_ref()).collect();
assert_eq!(texts, vec!["foobar", " baz"]);
assert_eq!(spans[0].style.bg, Some(Color::Yellow));
assert_eq!(spans[0].style.bg, Some(THEME.match_bg));
}

#[test]
Expand Down Expand Up @@ -333,13 +334,13 @@ mod tests {
for x in inner.x..inner.x + inner.width {
assert_eq!(
buffer[(x, selected_y)].style().bg,
Some(Color::Cyan),
"selected row x={x} should have cyan background"
Some(THEME.selected_bg),
"selected row x={x} should have the selected background"
);
}
assert!(
(inner.x..inner.x + inner.width)
.all(|x| buffer[(x, unselected_y)].style().bg != Some(Color::Cyan))
.all(|x| buffer[(x, unselected_y)].style().bg != Some(THEME.selected_bg))
);
}

Expand Down Expand Up @@ -405,7 +406,7 @@ mod tests {
assert!(summary.contains(
"tokens 31 input 10 output 9 cache r/w 6/4 reasoning 2 | time 2m | user msgs 2/3"
));
assert_eq!(terminal.backend().buffer()[(2, 1)].fg, Color::Green);
assert_eq!(terminal.backend().buffer()[(2, 1)].fg, THEME.summary);
}

#[test]
Expand Down
Loading