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 workspace_trust: String,
234 pub tools: String,
235 pub mcp: String,
236 pub primary_agent: Option<String>,
237 pub primary_agent_color: Option<String>,
238 pub highlights: Vec<InlineHeaderHighlight>,
239 pub subagent_badges: Vec<InlineHeaderBadge>,
240}
241
242impl Default for InlineHeaderContext {
243 fn default() -> Self {
244 let version = env!("CARGO_PKG_VERSION").to_string();
245 Self {
246 app_name: "VT Code".to_string(),
252 provider: "Provider: unavailable".to_string(),
253 model: "Model: unavailable".to_string(),
254 context_window_size: None,
255 version,
256 search_tools: None,
257 persistent_memory: None,
258 pr_review: None,
259 git: "git: unavailable".to_string(),
260 reasoning: "unavailable".to_string(),
261 reasoning_stage: None,
262 workspace_trust: "Trust: unavailable".to_string(),
263 tools: "Tools: unavailable".to_string(),
264 mcp: "MCP: unavailable".to_string(),
265 primary_agent: None,
266 primary_agent_color: None,
267 highlights: Vec::new(),
268 subagent_badges: Vec::new(),
269 }
270 }
271}
272
273fn convert_ansi_color(color: AnsiColorEnum) -> Option<AnsiColorEnum> {
278 Some(match color {
279 AnsiColorEnum::Ansi(ansi) => AnsiColorEnum::Ansi(ansi),
280 AnsiColorEnum::Ansi256(value) => AnsiColorEnum::Ansi256(value),
281 AnsiColorEnum::Rgb(rgb) => AnsiColorEnum::Rgb(rgb),
282 })
283}
284
285fn convert_style_color(style: &AnsiStyle) -> Option<AnsiColorEnum> {
286 style.get_fg_color().and_then(convert_ansi_color)
287}
288
289fn convert_style_bg_color(style: &AnsiStyle) -> Option<AnsiColorEnum> {
290 style.get_bg_color().and_then(convert_ansi_color)
291}
292
293pub fn convert_style(style: AnsiStyle) -> InlineTextStyle {
295 InlineTextStyle {
296 color: convert_style_color(&style),
297 bg_color: convert_style_bg_color(&style),
298 effects: style.get_effects(),
299 }
300}
301
302pub fn theme_from_color_fields(
304 foreground: AnsiColorEnum,
305 background: AnsiColorEnum,
306 primary: AnsiStyle,
307 secondary: AnsiStyle,
308 tool: AnsiStyle,
309 tool_detail: AnsiStyle,
310 pty_output: AnsiStyle,
311 error: AnsiStyle,
312 warning: AnsiStyle,
313) -> InlineTheme {
314 InlineTheme {
315 foreground: convert_ansi_color(foreground),
316 background: convert_ansi_color(background),
317 primary: convert_style_color(&primary),
318 secondary: convert_style_color(&secondary),
319 tool_accent: convert_style_color(&tool),
320 tool_body: convert_style_color(&tool_detail),
321 pty_body: convert_style_color(&pty_output),
322 error: convert_style_color(&error),
323 warning: convert_style_color(&warning),
324 }
325}