vtcode_commons/ui_protocol/
style.rs1use std::sync::Arc;
4
5use anstyle::{Color as AnsiColorEnum, Effects, Style as AnsiStyle};
6
7#[derive(Clone, Debug, Default, PartialEq)]
9pub struct InlineTextStyle {
10 pub color: Option<AnsiColorEnum>,
11 pub bg_color: Option<AnsiColorEnum>,
12 pub effects: Effects,
13}
14
15impl InlineTextStyle {
16 #[must_use]
17 pub fn with_color(mut self, color: Option<AnsiColorEnum>) -> Self {
18 self.color = color;
19 self
20 }
21
22 #[must_use]
23 pub fn with_bg_color(mut self, color: Option<AnsiColorEnum>) -> Self {
24 self.bg_color = color;
25 self
26 }
27
28 #[must_use]
29 pub fn merge_color(mut self, fallback: Option<AnsiColorEnum>) -> Self {
30 if self.color.is_none() {
31 self.color = fallback;
32 }
33 self
34 }
35
36 #[must_use]
37 pub fn merge_bg_color(mut self, fallback: Option<AnsiColorEnum>) -> Self {
38 if self.bg_color.is_none() {
39 self.bg_color = fallback;
40 }
41 self
42 }
43
44 #[must_use]
45 pub fn bold(mut self) -> Self {
46 self.effects |= Effects::BOLD;
47 self
48 }
49
50 #[must_use]
51 pub fn italic(mut self) -> Self {
52 self.effects |= Effects::ITALIC;
53 self
54 }
55
56 #[must_use]
57 pub fn underline(mut self) -> Self {
58 self.effects |= Effects::UNDERLINE;
59 self
60 }
61
62 #[must_use]
63 pub fn dim(mut self) -> Self {
64 self.effects |= Effects::DIMMED;
65 self
66 }
67
68 #[must_use]
69 pub fn to_ansi_style(&self, fallback: Option<AnsiColorEnum>) -> AnsiStyle {
70 let mut style = AnsiStyle::new();
71 if let Some(color) = self.color.or(fallback) {
72 style = style.fg_color(Some(color));
73 }
74 if let Some(bg) = self.bg_color {
75 style = style.bg_color(Some(bg));
76 }
77 if self.effects.contains(Effects::BOLD) {
78 style = style.bold();
79 }
80 if self.effects.contains(Effects::ITALIC) {
81 style = style.italic();
82 }
83 if self.effects.contains(Effects::UNDERLINE) {
84 style = style.underline();
85 }
86 if self.effects.contains(Effects::DIMMED) {
87 style = style.dimmed();
88 }
89 style
90 }
91}
92
93#[derive(Clone, Debug, Default)]
95pub struct InlineSegment {
96 pub text: String,
97 pub style: Arc<InlineTextStyle>,
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
102pub enum InlineLinkTarget {
103 Url(String),
104}
105
106#[derive(Clone, Debug, PartialEq, Eq)]
108pub struct InlineLinkRange {
109 pub start: usize,
110 pub end: usize,
111 pub target: InlineLinkTarget,
112}
113
114#[derive(Clone, Debug, Default)]
116pub struct InlineTheme {
117 pub foreground: Option<AnsiColorEnum>,
118 pub background: Option<AnsiColorEnum>,
119 pub primary: Option<AnsiColorEnum>,
120 pub secondary: Option<AnsiColorEnum>,
121 pub tool_accent: Option<AnsiColorEnum>,
122 pub tool_body: Option<AnsiColorEnum>,
123 pub pty_body: Option<AnsiColorEnum>,
124 pub error: Option<AnsiColorEnum>,
125 pub warning: Option<AnsiColorEnum>,
126}
127
128#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
137pub enum InlineTone {
138 #[default]
139 Neutral,
140 Accent,
141 Success,
142 Warning,
143 Danger,
144 Current,
146}
147
148#[derive(Clone, Debug, PartialEq, Eq)]
152pub struct InlineStatus {
153 pub tone: InlineTone,
154 pub message: String,
155}
156
157impl InlineStatus {
158 #[must_use]
159 pub fn new(tone: InlineTone, message: impl Into<String>) -> Self {
160 Self { tone, message: message.into() }
161 }
162
163 #[must_use]
164 pub fn success(message: impl Into<String>) -> Self {
165 Self::new(InlineTone::Success, message)
166 }
167
168 #[must_use]
169 pub fn warning(message: impl Into<String>) -> Self {
170 Self::new(InlineTone::Warning, message)
171 }
172
173 #[must_use]
174 pub fn error(message: impl Into<String>) -> Self {
175 Self::new(InlineTone::Danger, message)
176 }
177
178 #[must_use]
179 pub fn info(message: impl Into<String>) -> Self {
180 Self::new(InlineTone::Accent, message)
181 }
182}
183
184#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
190pub enum InlineHeaderStatusTone {
191 #[default]
192 Ready,
193 Warning,
194 Error,
195}
196
197#[derive(Clone, Debug, Default, PartialEq, Eq)]
199pub struct InlineHeaderStatusBadge {
200 pub text: String,
201 pub tone: InlineHeaderStatusTone,
202}
203
204#[derive(Clone, Debug, Default, PartialEq)]
206pub struct InlineHeaderBadge {
207 pub text: String,
208 pub style: InlineTextStyle,
209 pub full_background: bool,
210}
211
212#[derive(Clone, Debug, Default, PartialEq, Eq)]
214pub struct InlineHeaderHighlight {
215 pub title: String,
216 pub lines: Vec<String>,
217}
218
219#[derive(Clone, Debug)]
221pub struct InlineHeaderContext {
222 pub app_name: String,
223 pub provider: String,
224 pub model: String,
225 pub context_window_size: Option<usize>,
226 pub version: String,
227 pub search_tools: Option<InlineHeaderStatusBadge>,
228 pub persistent_memory: Option<InlineHeaderStatusBadge>,
229 pub pr_review: Option<InlineHeaderStatusBadge>,
230 pub git: String,
231 pub reasoning: String,
232 pub reasoning_stage: Option<String>,
233 pub service_tier: Option<String>,
236 pub workspace_trust: String,
237 pub tools: String,
238 pub mcp: String,
239 pub primary_agent: Option<String>,
240 pub primary_agent_color: Option<String>,
241 pub highlights: Vec<InlineHeaderHighlight>,
242 pub subagent_badges: Vec<InlineHeaderBadge>,
243}
244
245impl Default for InlineHeaderContext {
246 fn default() -> Self {
247 let version = env!("CARGO_PKG_VERSION").to_string();
248 Self {
249 app_name: "VT Code".to_string(),
255 provider: "Provider: unavailable".to_string(),
256 model: "Model: unavailable".to_string(),
257 context_window_size: None,
258 version,
259 search_tools: None,
260 persistent_memory: None,
261 pr_review: None,
262 git: "git: unavailable".to_string(),
263 reasoning: "unavailable".to_string(),
264 reasoning_stage: None,
265 service_tier: None,
266 workspace_trust: "Trust: unavailable".to_string(),
267 tools: "Tools: unavailable".to_string(),
268 mcp: "MCP: unavailable".to_string(),
269 primary_agent: None,
270 primary_agent_color: None,
271 highlights: Vec::new(),
272 subagent_badges: Vec::new(),
273 }
274 }
275}
276
277fn convert_ansi_color(color: AnsiColorEnum) -> Option<AnsiColorEnum> {
282 Some(match color {
283 AnsiColorEnum::Ansi(ansi) => AnsiColorEnum::Ansi(ansi),
284 AnsiColorEnum::Ansi256(value) => AnsiColorEnum::Ansi256(value),
285 AnsiColorEnum::Rgb(rgb) => AnsiColorEnum::Rgb(rgb),
286 })
287}
288
289fn convert_style_color(style: &AnsiStyle) -> Option<AnsiColorEnum> {
290 style.get_fg_color().and_then(convert_ansi_color)
291}
292
293fn convert_style_bg_color(style: &AnsiStyle) -> Option<AnsiColorEnum> {
294 style.get_bg_color().and_then(convert_ansi_color)
295}
296
297pub fn convert_style(style: AnsiStyle) -> InlineTextStyle {
299 InlineTextStyle {
300 color: convert_style_color(&style),
301 bg_color: convert_style_bg_color(&style),
302 effects: style.get_effects(),
303 }
304}
305
306pub fn theme_from_color_fields(
308 foreground: AnsiColorEnum,
309 background: AnsiColorEnum,
310 primary: AnsiStyle,
311 secondary: AnsiStyle,
312 tool: AnsiStyle,
313 tool_detail: AnsiStyle,
314 pty_output: AnsiStyle,
315 error: AnsiStyle,
316 warning: AnsiStyle,
317) -> InlineTheme {
318 InlineTheme {
319 foreground: convert_ansi_color(foreground),
320 background: convert_ansi_color(background),
321 primary: convert_style_color(&primary),
322 secondary: convert_style_color(&secondary),
323 tool_accent: convert_style_color(&tool),
324 tool_body: convert_style_color(&tool_detail),
325 pty_body: convert_style_color(&pty_output),
326 error: convert_style_color(&error),
327 warning: convert_style_color(&warning),
328 }
329}