返回 DeepSeek-TUI-2026
deepseek_theme.rs
根目录 / crates / tui / src / deepseek_theme.rs
1 //! Whale/DeepSeek terminal theme tokens.
2 //!
3 //! A small, deliberately flat module that names the color, border, and
4 //! padding choices the TUI is already making. All values match the dark
5 //! palette previously hard-coded against [`crate::palette`]; a single
6 //! source-of-truth change here can swap the skin later. Visible output
7 //! is not changed by introducing this module.
8 //!
9 //! The only consumers today are the plan and tool cell renderers in
10 //! [`crate::tui::history`] and the sidebar section chrome in
11 //! [`crate::tui::ui`]. All other call sites continue to use [`crate::palette`]
12 //! directly until they are migrated in a later slice.
13
14 use ratatui::style::{Color, Modifier, Style};
15 use ratatui::widgets::{BorderType, Borders, Padding};
16
17 use crate::palette;
18 use crate::tui::history::ToolStatus;
19
20 /// Visual variant exposed by the theme.
21 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
22 pub enum Variant {
23 Dark,
24 }
25
26 /// Centralized visual tokens for sidebar, plan, and tool rendering.
27 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
28 pub struct Theme {
29 pub variant: Variant,
30
31 // Sidebar / section chrome
32 pub section_borders: Borders,
33 pub section_border_type: BorderType,
34 pub section_border_color: Color,
35 pub section_bg: Color,
36 pub section_title_color: Color,
37 pub section_padding: Padding,
38
39 // Tool cell color tokens
40 pub tool_title_color: Color,
41 pub tool_value_color: Color,
42 pub tool_label_color: Color,
43 pub tool_running_accent: Color,
44 pub tool_success_accent: Color,
45 pub tool_failed_accent: Color,
46
47 // Plan cell color tokens
48 pub plan_progress_color: Color,
49 pub plan_summary_color: Color,
50 pub plan_explanation_color: Color,
51 pub plan_pending_color: Color,
52 pub plan_in_progress_color: Color,
53 pub plan_completed_color: Color,
54 }
55
56 impl Theme {
57 /// The current dark theme. Visible output today uses these values.
58 #[must_use]
59 pub const fn dark() -> Self {
60 Self {
61 variant: Variant::Dark,
62 section_borders: Borders::ALL,
63 section_border_type: BorderType::Plain,
64 section_border_color: palette::BORDER_COLOR,
65 section_bg: palette::DEEPSEEK_INK,
66 section_title_color: palette::DEEPSEEK_BLUE,
67 // Horizontal padding only. `Padding::uniform(1)` ate two rows of
68 // each sidebar panel — for compact terminals where Plan/Todos/Tasks
69 // get ~3 rows total via the 25% layout split, that left zero rows
70 // for content (#63 follow-up: panels rendered as empty boxes even
71 // when "No todos" / "No active plan" should have shown).
72 section_padding: Padding::horizontal(1),
73 tool_title_color: palette::TEXT_SOFT,
74 tool_value_color: palette::TEXT_MUTED,
75 tool_label_color: palette::TEXT_DIM,
76 tool_running_accent: palette::ACCENT_TOOL_LIVE,
77 tool_success_accent: palette::TEXT_DIM,
78 tool_failed_accent: palette::ACCENT_TOOL_ISSUE,
79 plan_progress_color: palette::STATUS_SUCCESS,
80 plan_summary_color: palette::TEXT_MUTED,
81 plan_explanation_color: palette::TEXT_DIM,
82 plan_pending_color: palette::TEXT_MUTED,
83 plan_in_progress_color: palette::STATUS_WARNING,
84 plan_completed_color: palette::STATUS_SUCCESS,
85 }
86 }
87
88 /// Pick the right tool accent for a given [`ToolStatus`].
89 #[must_use]
90 pub const fn tool_status_color(self, status: ToolStatus) -> Color {
91 match status {
92 ToolStatus::Running => self.tool_running_accent,
93 ToolStatus::Success => self.tool_success_accent,
94 ToolStatus::Failed => self.tool_failed_accent,
95 }
96 }
97
98 /// Bold tool title style (e.g. "Plan", "Shell").
99 #[must_use]
100 pub fn tool_title_style(self) -> Style {
101 Style::default()
102 .fg(self.tool_title_color)
103 .add_modifier(Modifier::BOLD)
104 }
105
106 /// Right-side status text ("running", "done", "issue") style.
107 #[must_use]
108 pub fn tool_status_style(self, status: ToolStatus) -> Style {
109 Style::default().fg(self.tool_status_color(status))
110 }
111
112 /// Detail label style ("command:", "time:", step markers).
113 #[must_use]
114 pub fn tool_label_style(self) -> Style {
115 Style::default().fg(self.tool_label_color)
116 }
117
118 /// Default value style for tool detail rows.
119 #[must_use]
120 pub fn tool_value_style(self) -> Style {
121 Style::default().fg(self.tool_value_color)
122 }
123 }
124
125 /// Returns the active theme used by the TUI today.
126 ///
127 /// Today this is always `Theme::dark()`. A future PR can wire this to an
128 /// `App` field or a config setting in five lines.
129 #[must_use]
130 pub const fn active_theme() -> Theme {
131 Theme::dark()
132 }
133
134 #[cfg(test)]
135 mod tests {
136 use super::{Theme, Variant, active_theme};
137 use crate::palette;
138 use crate::tui::history::ToolStatus;
139
140 #[test]
141 fn active_theme_returns_dark() {
142 assert_eq!(active_theme(), Theme::dark());
143 }
144
145 #[test]
146 fn dark_theme_matches_existing_palette_choices() {
147 let theme = Theme::dark();
148 assert_eq!(theme.variant, Variant::Dark);
149 assert_eq!(theme.section_border_color, palette::BORDER_COLOR);
150 assert_eq!(theme.section_bg, palette::DEEPSEEK_INK);
151 assert_eq!(theme.section_title_color, palette::DEEPSEEK_BLUE);
152 assert_eq!(theme.tool_title_color, palette::TEXT_SOFT);
153 assert_eq!(theme.tool_value_color, palette::TEXT_MUTED);
154 assert_eq!(theme.tool_label_color, palette::TEXT_DIM);
155 assert_eq!(theme.tool_running_accent, palette::ACCENT_TOOL_LIVE);
156 assert_eq!(theme.tool_success_accent, palette::TEXT_DIM);
157 assert_eq!(theme.tool_failed_accent, palette::ACCENT_TOOL_ISSUE);
158 }
159
160 #[test]
161 fn tool_status_color_maps_each_status() {
162 let theme = Theme::dark();
163 assert_eq!(
164 theme.tool_status_color(ToolStatus::Running),
165 theme.tool_running_accent
166 );
167 assert_eq!(
168 theme.tool_status_color(ToolStatus::Success),
169 theme.tool_success_accent
170 );
171 assert_eq!(
172 theme.tool_status_color(ToolStatus::Failed),
173 theme.tool_failed_accent
174 );
175 }
176 }
177
177 lines RUST