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