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