1pub const TOOL_OUTPUT_MODE_COMPACT: &str = "compact";
2pub const TOOL_OUTPUT_MODE_FULL: &str = "full";
3pub const DEFAULT_INLINE_VIEWPORT_ROWS: u16 = 16;
4pub const DEFAULT_REASONING_VISIBLE: bool = true;
5pub const SLASH_SUGGESTION_LIMIT: usize = 50; pub const SLASH_PALETTE_MIN_WIDTH: u16 = 40;
7pub const SLASH_PALETTE_MIN_HEIGHT: u16 = 9;
8pub const SLASH_PALETTE_HORIZONTAL_MARGIN: u16 = 8;
9pub const SLASH_PALETTE_TOP_OFFSET: u16 = 3;
10pub const SLASH_PALETTE_CONTENT_PADDING: u16 = 6;
11pub const SLASH_PALETTE_HINT_PRIMARY: &str = "Type to filter slash commands.";
12pub const SLASH_PALETTE_HINT_SECONDARY: &str = "Press Enter to apply • Esc to dismiss.";
13pub const MODAL_MIN_WIDTH: u16 = 36;
14pub const MODAL_MIN_HEIGHT: u16 = 16;
15pub const MODAL_LIST_MIN_HEIGHT: u16 = 12;
16pub const MODAL_WIDTH_RATIO: f32 = 0.6;
17pub const MODAL_HEIGHT_RATIO: f32 = 0.6;
18pub const MODAL_MAX_WIDTH_RATIO: f32 = 0.9;
19pub const MODAL_MAX_HEIGHT_RATIO: f32 = 0.8;
20pub const MODAL_CONTENT_HORIZONTAL_PADDING: u16 = 8;
21pub const MODAL_CONTENT_VERTICAL_PADDING: u16 = 6;
22pub const MODAL_INSTRUCTIONS_TITLE: &str = "";
23pub const MODAL_INSTRUCTIONS_BULLET: &str = "•";
24pub const INLINE_HEADER_HEIGHT: u16 = 3;
25pub const INLINE_INPUT_HEIGHT: u16 = 4;
26pub const INLINE_INPUT_PADDING_HORIZONTAL: u16 = 1;
27pub const INLINE_INPUT_PADDING_VERTICAL: u16 = 1;
28pub const INLINE_INPUT_MAX_LINES: usize = 10;
29pub const INLINE_NAVIGATION_PERCENT: u16 = 28;
30pub const INLINE_NAVIGATION_MIN_WIDTH: u16 = 24;
31pub const INLINE_CONTENT_MIN_WIDTH: u16 = 48;
32pub const INLINE_STACKED_NAVIGATION_PERCENT: u16 = INLINE_NAVIGATION_PERCENT;
33pub const INLINE_SCROLLBAR_EDGE_PADDING: u16 = 1;
34pub const INLINE_TRANSCRIPT_BOTTOM_PADDING: u16 = 2;
41pub const INLINE_PREVIEW_MAX_CHARS: usize = 56;
42pub const INLINE_PREVIEW_ELLIPSIS: &str = "…";
43pub const INLINE_PASTE_COLLAPSE_LINE_THRESHOLD: usize = 10;
44pub const HEADER_HIGHLIGHT_PREVIEW_MAX_CHARS: usize = 48;
45pub const INLINE_AGENT_MESSAGE_LEFT_PADDING: &str = " ";
46pub const INLINE_AGENT_QUOTE_PREFIX: &str = " •";
47pub const INLINE_USER_MESSAGE_DIVIDER_SYMBOL: &str = "─";
48
49pub const SCROLL_INDICATOR_FORMAT: &str = "↕";
51pub const SCROLL_INDICATOR_ENABLED: bool = false;
53
54pub const INLINE_BLOCK_TOP_LEFT: &str = "╭";
55pub const INLINE_BLOCK_TOP_RIGHT: &str = "╮";
56pub const INLINE_BLOCK_BODY_LEFT: &str = "│";
57pub const INLINE_BLOCK_BODY_RIGHT: &str = "│";
58pub const INLINE_BLOCK_BOTTOM_LEFT: &str = "╰";
59pub const INLINE_BLOCK_BOTTOM_RIGHT: &str = "╯";
60pub const INLINE_BLOCK_HORIZONTAL: &str = "─";
61pub const INLINE_TOOL_HEADER_LABEL: &str = "Tool";
62pub const INLINE_TOOL_ACTION_PREFIX: &str = "→";
63pub const INLINE_TOOL_DETAIL_PREFIX: &str = "↳";
64pub const INLINE_PTY_HEADER_LABEL: &str = "Terminal";
65pub const INLINE_PTY_RUNNING_LABEL: &str = "running";
66pub const INLINE_PTY_STATUS_LIVE: &str = "LIVE";
67pub const INLINE_PTY_STATUS_DONE: &str = "DONE";
68pub const INLINE_PTY_PLACEHOLDER: &str = "Terminal output";
69pub const MODAL_LIST_HIGHLIGHT_SYMBOL: &str = "│";
70pub const MODAL_LIST_HIGHLIGHT_FULL: &str = "│ ";
71pub const MODAL_LIST_SUMMARY_FILTER_LABEL: &str = "Filter";
72pub const MODAL_LIST_SUMMARY_SEPARATOR: &str = " • ";
73pub const MODAL_LIST_SUMMARY_MATCHES_LABEL: &str = "Matches";
74pub const MODAL_LIST_SUMMARY_TOTAL_LABEL: &str = "of";
75pub const MODAL_LIST_SUMMARY_NO_MATCHES: &str = "No matches";
76pub const MODAL_LIST_SUMMARY_RESET_HINT: &str = "Press Esc to reset";
77pub const MODAL_LIST_NO_RESULTS_MESSAGE: &str = "No matching options";
78pub const HEADER_VERSION_PROMPT: &str = "> ";
79pub const HEADER_VERSION_PREFIX: &str = "VT Code";
80pub const HEADER_VERSION_LEFT_DELIMITER: &str = "(";
81pub const HEADER_VERSION_RIGHT_DELIMITER: &str = ")";
82pub const HEADER_PROVIDER_PREFIX: &str = "Provider: ";
83pub const HEADER_MODEL_PREFIX: &str = "Model: ";
84pub const HEADER_SERVICE_TIER_PREFIX: &str = "Tier: ";
85pub const HEADER_REASONING_PREFIX: &str = "";
86pub const HEADER_TRUST_PREFIX: &str = "Trust: ";
87pub const HEADER_TOOLS_PREFIX: &str = "Tools: ";
88pub const HEADER_MCP_PREFIX: &str = "MCP: ";
89pub const HEADER_GIT_PREFIX: &str = "git: ";
90pub const HEADER_GIT_CLEAN_SUFFIX: &str = "✓";
91pub const HEADER_GIT_DIRTY_SUFFIX: &str = "*";
92pub const HEADER_UNKNOWN_PLACEHOLDER: &str = "unavailable";
93pub const HEADER_STATUS_LABEL: &str = "Status";
94pub const HEADER_STATUS_ACTIVE: &str = "Active";
95pub const HEADER_STATUS_PAUSED: &str = "Paused";
96pub const HEADER_MESSAGES_LABEL: &str = "Messages";
97pub const HEADER_INPUT_LABEL: &str = "Input";
98pub const HEADER_INPUT_ENABLED: &str = "Enabled";
99pub const HEADER_INPUT_DISABLED: &str = "Disabled";
100pub const INLINE_USER_PREFIX: &str = " ";
101pub const CHAT_INPUT_PLACEHOLDER_BOOTSTRAP: &str =
102 "Type a request… Tab switch agent • @files /commands • Enter run/steer • Ctrl+Enter queue";
103pub const CHAT_INPUT_PLACEHOLDER_FOLLOW_UP: &str =
104 "Continue… Tab switch agent • @files /commands • Enter run/steer • Ctrl+Enter queue";
105pub const CHAT_INPUT_PLACEHOLDER_INTERRUPTED: &str =
106 "Interrupted · What should VT Code do instead? • Esc cancel • Ctrl+C interrupt • Ctrl+D exit";
107pub const HEADER_SHORTCUT_HINT: &str =
108 "? help • / command • @ file • Tab agent • Enter run/steer • Ctrl+Enter queue • Esc cancel • Ctrl+C interrupt";
109pub const HEADER_META_SEPARATOR: &str = " ";
110pub const WELCOME_TEXT_WIDTH: usize = 80;
111pub const WELCOME_SHORTCUT_SECTION_TITLE: &str = "Keyboard Shortcuts";
112pub const WELCOME_SHORTCUT_HINT_PREFIX: &str = "Shortcuts:";
113pub const WELCOME_SHORTCUT_SEPARATOR: &str = "•";
114pub const WELCOME_SHORTCUT_INDENT: &str = " ";
115pub const WELCOME_SLASH_COMMAND_SECTION_TITLE: &str = "Slash Commands";
116pub const WELCOME_SLASH_COMMAND_LIMIT: usize = 6;
117pub const WELCOME_SLASH_COMMAND_PREFIX: &str = "/";
118pub const WELCOME_SLASH_COMMAND_INTRO: &str = "";
119pub const WELCOME_SLASH_COMMAND_INDENT: &str = " ";
120pub const NAVIGATION_BLOCK_TITLE: &str = "Timeline";
121pub const NAVIGATION_BLOCK_SHORTCUT_NOTE: &str = "Ctrl+T";
122pub const NAVIGATION_EMPTY_LABEL: &str = "Waiting for activity";
123pub const NAVIGATION_INDEX_PREFIX: &str = "#";
124pub const NAVIGATION_LABEL_AGENT: &str = "Agent";
125pub const NAVIGATION_LABEL_ERROR: &str = "Error";
126pub const NAVIGATION_LABEL_INFO: &str = "Info";
127pub const NAVIGATION_LABEL_POLICY: &str = "Policy";
128pub const NAVIGATION_LABEL_TOOL: &str = "Tool";
129pub const NAVIGATION_LABEL_USER: &str = "User";
130pub const NAVIGATION_LABEL_PTY: &str = "PTY";
131pub const PLAN_BLOCK_TITLE: &str = "TODOs";
132pub const PLAN_STATUS_EMPTY: &str = "No TODOs";
133pub const PLAN_STATUS_IN_PROGRESS: &str = "In progress";
134pub const PLAN_STATUS_DONE: &str = "Done";
135pub const PLAN_IN_PROGRESS_NOTE: &str = "in progress";
136pub const SUGGESTION_BLOCK_TITLE: &str = "Slash Commands";
137pub const STATUS_LINE_MODE: &str = "auto";
138pub(crate) const STATUS_LINE_REFRESH_INTERVAL_MS: u64 = 1000;
139pub(crate) const STATUS_LINE_COMMAND_TIMEOUT_MS: u64 = 200;
140
141const AGENT_HUE_NAMES: &[&str] = &["red", "green", "blue", "magenta", "yellow", "cyan"];
156
157pub const AGENT_COLOR_BUILD: &str = "#73a18e";
159pub const AGENT_COLOR_AUTO: &str = "green";
161pub const AGENT_COLOR_PLAN: &str = "blue";
163pub const AGENT_COLOR_DUCK: &str = "magenta";
165
166const AGENT_MODE_HUE: &[(&str, &str)] = &[
169 ("build", AGENT_COLOR_BUILD),
170 ("auto", AGENT_COLOR_AUTO),
171 ("plan", AGENT_COLOR_PLAN),
172 ("duck", AGENT_COLOR_DUCK),
173];
174
175pub fn agent_mode_hue(mode: &str) -> Option<&'static str> {
178 AGENT_MODE_HUE.iter().find(|(m, _)| *m == mode).map(|(_, h)| *h)
179}
180
181pub const TUI_ACTIVE_TICK_RATE_HZ: f64 = 60.0;
189pub const TUI_IDLE_TICK_RATE_HZ: f64 = 4.0;
191pub const TUI_ACTIVE_TIMEOUT_MS: u64 = 500;
194pub const TUI_SHIMMER_FRAME_INTERVAL_MS: u64 = 33;
196pub const TUI_SHIMMER_SWEEP_DURATION_MS: u64 = 2000;
198pub const TUI_SCROLL_CURSOR_STEADY_MS: u64 = 250;
200
201pub const TUI_MAX_VIEWPORT_WIDTH: u16 = 500;
206pub const TUI_MAX_VIEWPORT_HEIGHT: u16 = 200;
208
209pub const THEME_MIN_CONTRAST_RATIO: f32 = 4.5;
211pub const THEME_FOREGROUND_LIGHTEN_RATIO: f32 = 0.25;
212pub const THEME_SECONDARY_LIGHTEN_RATIO: f32 = 0.2;
213pub const THEME_MIX_RATIO: f32 = 0.35;
214pub const THEME_TOOL_BODY_MIX_RATIO: f32 = 0.35;
215pub const THEME_TOOL_BODY_LIGHTEN_RATIO: f32 = 0.2;
216pub const THEME_RESPONSE_COLOR_LIGHTEN_RATIO: f32 = 0.15;
217pub const THEME_REASONING_COLOR_LIGHTEN_RATIO: f32 = 0.3;
218pub const THEME_INPUT_BACKGROUND_MIX_RATIO: f32 = 0.08;
219pub const THEME_USER_COLOR_LIGHTEN_RATIO: f32 = 0.2;
220pub const THEME_SECONDARY_USER_COLOR_LIGHTEN_RATIO: f32 = 0.4;
221pub const THEME_PRIMARY_STATUS_LIGHTEN_RATIO: f32 = 0.35;
222pub const THEME_PRIMARY_STATUS_SECONDARY_LIGHTEN_RATIO: f32 = 0.5;
223pub const THEME_LOGO_ACCENT_BANNER_LIGHTEN_RATIO: f32 = 0.35;
224pub const THEME_LOGO_ACCENT_BANNER_SECONDARY_LIGHTEN_RATIO: f32 = 0.25;
225
226pub const THEME_COLOR_WHITE_RED: u8 = 0xFF;
228pub const THEME_COLOR_WHITE_GREEN: u8 = 0xFF;
229pub const THEME_COLOR_WHITE_BLUE: u8 = 0xFF;
230pub const THEME_MIX_RATIO_MIN: f32 = 0.0;
231pub const THEME_MIX_RATIO_MAX: f32 = 1.0;
232pub const THEME_BLEND_CLAMP_MIN: f32 = 0.0;
233pub const THEME_BLEND_CLAMP_MAX: f32 = 255.0;
234
235pub const THEME_RELATIVE_LUMINANCE_CUTOFF: f32 = 0.03928;
237pub const THEME_RELATIVE_LUMINANCE_LOW_FACTOR: f32 = 12.92;
238pub const THEME_RELATIVE_LUMINANCE_OFFSET: f32 = 0.055;
239pub const THEME_RELATIVE_LUMINANCE_EXPONENT: f32 = 2.4;
240pub const THEME_CONTRAST_RATIO_OFFSET: f32 = 0.05;
241pub const THEME_RED_LUMINANCE_COEFFICIENT: f32 = 0.2126;
242pub const THEME_GREEN_LUMINANCE_COEFFICIENT: f32 = 0.7152;
243pub const THEME_BLUE_LUMINANCE_COEFFICIENT: f32 = 0.0722;
244pub const THEME_LUMINANCE_LIGHTEN_RATIO: f32 = 0.2;
245pub const THEME_PTY_OUTPUT_MIX_RATIO: f32 = 0.50;
247
248pub const DIFF_LINE_NUMBER_R: u8 = 0x4a;
250pub const DIFF_LINE_NUMBER_G: u8 = 0x4a;
251pub const DIFF_LINE_NUMBER_B: u8 = 0x4a;
252pub const PLACEHOLDER_R: u8 = 0x88;
254pub const PLACEHOLDER_G: u8 = 0x88;
255pub const PLACEHOLDER_B: u8 = 0x88;
256
257pub const WCAG_AA_CONTRAST_RATIO: f32 = 4.5;
264
265pub const WCAG_AAA_CONTRAST_RATIO: f32 = 7.0;
267
268pub const WCAG_LARGE_TEXT_CONTRAST_RATIO: f32 = 3.0;
270
271pub const SAFE_ANSI_RED: u8 = 1;
277pub const SAFE_ANSI_GREEN: u8 = 2;
278pub const SAFE_ANSI_YELLOW: u8 = 3;
279pub const SAFE_ANSI_BLUE: u8 = 4;
280pub const SAFE_ANSI_MAGENTA: u8 = 5;
281pub const SAFE_ANSI_CYAN: u8 = 6;
282
283pub const SAFE_ANSI_BRIGHT_RED: u8 = 9;
287pub const SAFE_ANSI_BRIGHT_GREEN: u8 = 10;
288pub const SAFE_ANSI_BRIGHT_MAGENTA: u8 = 13;
289pub const SAFE_ANSI_BRIGHT_CYAN: u8 = 14;
290
291pub const SAFE_ANSI_COLORS: [u8; 10] = [
294 SAFE_ANSI_RED,
295 SAFE_ANSI_GREEN,
296 SAFE_ANSI_YELLOW,
297 SAFE_ANSI_BLUE,
298 SAFE_ANSI_MAGENTA,
299 SAFE_ANSI_CYAN,
300 SAFE_ANSI_BRIGHT_RED,
301 SAFE_ANSI_BRIGHT_GREEN,
302 SAFE_ANSI_BRIGHT_MAGENTA,
303 SAFE_ANSI_BRIGHT_CYAN,
304];
305
306pub const PROBLEMATIC_ANSI_COLORS: [u8; 6] = [0, 7, 8, 11, 12, 15];
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 const DARK_BG: (u8, u8, u8) = (0x18, 0x18, 0x18);
323 const LIGHT_BG: (u8, u8, u8) = (0xFD, 0xF6, 0xE3);
324
325 fn hex_to_rgb(hex: &str) -> Option<(u8, u8, u8)> {
328 let hex = hex.strip_prefix('#')?;
329 if hex.len() != 6 || !hex.bytes().all(|b| b.is_ascii_hexdigit()) {
330 return None;
331 }
332 let channel = |i: usize| u8::from_str_radix(&hex[i..i + 2], 16).ok();
333 Some((channel(0)?, channel(2)?, channel(4)?))
334 }
335
336 fn hue_rgb_on(hue: &str, light: bool) -> Option<(u8, u8, u8)> {
341 if let Some(rgb) = hex_to_rgb(hue) {
342 return Some(rgb);
343 }
344 let (d, l) = match hue {
345 "red" => ((0xFF, 0x5F, 0x5F), (0xCD, 0x00, 0x00)),
346 "green" => ((0x5F, 0xFF, 0x5F), (0x00, 0xCD, 0x00)),
347 "blue" => ((0x5F, 0x5F, 0xFF), (0x00, 0x00, 0xCD)),
348 "magenta" => ((0xFF, 0x5F, 0xFF), (0xCD, 0x00, 0xCD)),
349 "yellow" => ((0xFF, 0xFF, 0x5F), (0xCD, 0xCD, 0x00)),
350 "cyan" => ((0x5F, 0xFF, 0xFF), (0x00, 0xCD, 0xCD)),
351 _ => return None,
352 };
353 Some(if light { l } else { d })
354 }
355
356 fn channel_lin(c: u8) -> f32 {
357 let c = c as f32 / 255.0;
358 if c <= 0.03928 {
359 c / 12.92
360 } else {
361 ((c + 0.055) / 1.055).powf(2.4)
362 }
363 }
364
365 fn relative_luminance((r, g, b): (u8, u8, u8)) -> f32 {
366 0.2126 * channel_lin(r) + 0.7152 * channel_lin(g) + 0.0722 * channel_lin(b)
367 }
368
369 fn contrast_ratio(fg: (u8, u8, u8), bg: (u8, u8, u8)) -> f32 {
370 let l_fg = relative_luminance(fg);
371 let l_bg = relative_luminance(bg);
372 let (hi, lo) = if l_fg > l_bg { (l_fg, l_bg) } else { (l_bg, l_fg) };
373 (hi + 0.05) / (lo + 0.05)
374 }
375
376 const MIN_CONTRAST_DARK: f32 = 3.0;
382 const MIN_CONTRAST_LIGHT: f32 = 2.0;
383
384 fn assert_agent_hue_readable(name: &str, hue: &str, light: bool, bg: (u8, u8, u8)) {
385 let fg =
386 hue_rgb_on(hue, light).unwrap_or_else(|| panic!("{name} ({hue}) is not a supported standard ANSI hue"));
387 let cr = contrast_ratio(fg, bg);
388 let min = if light { MIN_CONTRAST_LIGHT } else { MIN_CONTRAST_DARK };
389 assert!(
390 cr >= min,
391 "{name} ({hue}) on {} bg contrast {cr:.2} below {min}",
392 if light { "light" } else { "dark" }
393 );
394 }
395
396 #[test]
397 fn agent_mode_color_tokens_are_supported() {
398 for (_, hue) in AGENT_MODE_HUE {
399 let supported = AGENT_HUE_NAMES.contains(hue) || hex_to_rgb(hue).is_some();
400 assert!(supported, "mode token {hue} is neither a standard ANSI hue nor #rrggbb");
401 }
402 }
403
404 #[test]
405 fn agent_mode_colors_are_readable_in_dark_and_light_terminals() {
406 for (mode, hue) in AGENT_MODE_HUE {
407 assert_agent_hue_readable(mode, hue, false, DARK_BG);
409 assert_agent_hue_readable(mode, hue, true, LIGHT_BG);
410 }
411 }
412
413 #[test]
416 fn agent_mode_colors_are_distinct() {
417 let hues: Vec<&str> = AGENT_MODE_HUE.iter().map(|(_, h)| *h).collect();
418 for i in 0..hues.len() {
419 for j in (i + 1)..hues.len() {
420 assert_ne!(hues[i], hues[j], "mode hues {} and {} are identical", hues[i], hues[j]);
421 }
422 }
423 }
424
425 #[test]
426 fn agent_mode_hue_resolves_mode_names() {
427 assert_eq!(agent_mode_hue("build"), Some(AGENT_COLOR_BUILD));
428 assert_eq!(agent_mode_hue("auto"), Some(AGENT_COLOR_AUTO));
429 assert_eq!(agent_mode_hue("plan"), Some(AGENT_COLOR_PLAN));
430 assert_eq!(agent_mode_hue("duck"), Some(AGENT_COLOR_DUCK));
431 assert_eq!(agent_mode_hue("unknown"), None);
432 }
433
434 #[test]
435 fn agent_mode_colors_are_wired_into_builtin_primary_agents() {
436 assert_eq!(AGENT_COLOR_BUILD, "#73a18e");
438 assert_eq!(AGENT_COLOR_AUTO, "green");
439 assert_eq!(AGENT_COLOR_PLAN, "blue");
440 assert_eq!(AGENT_COLOR_DUCK, "magenta");
441 }
442}