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)]
134pub enum InlineHeaderStatusTone {
135 #[default]
136 Ready,
137 Warning,
138 Error,
139}
140
141#[derive(Clone, Debug, Default, PartialEq, Eq)]
143pub struct InlineHeaderStatusBadge {
144 pub text: String,
145 pub tone: InlineHeaderStatusTone,
146}
147
148#[derive(Clone, Debug, Default, PartialEq)]
150pub struct InlineHeaderBadge {
151 pub text: String,
152 pub style: InlineTextStyle,
153 pub full_background: bool,
154}
155
156#[derive(Clone, Debug, Default, PartialEq, Eq)]
158pub struct InlineHeaderHighlight {
159 pub title: String,
160 pub lines: Vec<String>,
161}
162
163#[derive(Clone, Debug)]
165pub struct InlineHeaderContext {
166 pub app_name: String,
167 pub provider: String,
168 pub model: String,
169 pub context_window_size: Option<usize>,
170 pub version: String,
171 pub search_tools: Option<InlineHeaderStatusBadge>,
172 pub persistent_memory: Option<InlineHeaderStatusBadge>,
173 pub pr_review: Option<InlineHeaderStatusBadge>,
174 pub editor_context: Option<String>,
175 pub git: String,
176 pub reasoning: String,
177 pub reasoning_stage: Option<String>,
178 pub workspace_trust: String,
179 pub tools: String,
180 pub mcp: String,
181 pub primary_agent: Option<String>,
182 pub primary_agent_color: Option<String>,
183 pub highlights: Vec<InlineHeaderHighlight>,
184 pub subagent_badges: Vec<InlineHeaderBadge>,
185}
186
187impl Default for InlineHeaderContext {
188 fn default() -> Self {
189 let version = env!("CARGO_PKG_VERSION").to_string();
190 Self {
191 app_name: "App".to_string(),
192 provider: "Provider: unavailable".to_string(),
193 model: "Model: unavailable".to_string(),
194 context_window_size: None,
195 version,
196 search_tools: None,
197 persistent_memory: None,
198 pr_review: None,
199 editor_context: None,
200 git: "git: unavailable".to_string(),
201 reasoning: "unavailable".to_string(),
202 reasoning_stage: None,
203 workspace_trust: "Trust: unavailable".to_string(),
204 tools: "Tools: unavailable".to_string(),
205 mcp: "MCP: unavailable".to_string(),
206 primary_agent: None,
207 primary_agent_color: None,
208 highlights: Vec::new(),
209 subagent_badges: Vec::new(),
210 }
211 }
212}
213
214fn convert_ansi_color(color: AnsiColorEnum) -> Option<AnsiColorEnum> {
219 Some(match color {
220 AnsiColorEnum::Ansi(ansi) => AnsiColorEnum::Ansi(ansi),
221 AnsiColorEnum::Ansi256(value) => AnsiColorEnum::Ansi256(value),
222 AnsiColorEnum::Rgb(rgb) => AnsiColorEnum::Rgb(rgb),
223 })
224}
225
226fn convert_style_color(style: &AnsiStyle) -> Option<AnsiColorEnum> {
227 style.get_fg_color().and_then(convert_ansi_color)
228}
229
230fn convert_style_bg_color(style: &AnsiStyle) -> Option<AnsiColorEnum> {
231 style.get_bg_color().and_then(convert_ansi_color)
232}
233
234pub fn convert_style(style: AnsiStyle) -> InlineTextStyle {
236 InlineTextStyle {
237 color: convert_style_color(&style),
238 bg_color: convert_style_bg_color(&style),
239 effects: style.get_effects(),
240 }
241}
242
243pub fn theme_from_color_fields(
245 foreground: AnsiColorEnum,
246 background: AnsiColorEnum,
247 primary: AnsiStyle,
248 secondary: AnsiStyle,
249 tool: AnsiStyle,
250 tool_detail: AnsiStyle,
251 pty_output: AnsiStyle,
252 error: AnsiStyle,
253 warning: AnsiStyle,
254) -> InlineTheme {
255 InlineTheme {
256 foreground: convert_ansi_color(foreground),
257 background: convert_ansi_color(background),
258 primary: convert_style_color(&primary),
259 secondary: convert_style_color(&secondary),
260 tool_accent: convert_style_color(&tool),
261 tool_body: convert_style_color(&tool_detail),
262 pty_body: convert_style_color(&pty_output),
263 error: convert_style_color(&error),
264 warning: convert_style_color(&warning),
265 }
266}