diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 6bd3ceb4cb..c910f95442 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2317,6 +2317,7 @@ dependencies = [ "tracing", "tracing-appender", "tracing-subscriber", + "two-face", "unicode-segmentation", "unicode-width 0.2.1", "url", @@ -9759,6 +9760,17 @@ dependencies = [ "utf-8", ] +[[package]] +name = "two-face" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b285c51f8a6ade109ed4566d33ac4fb289fb5d6cf87ed70908a5eaf65e948e34" +dependencies = [ + "serde", + "serde_derive", + "syntect", +] + [[package]] name = "type-map" version = "0.5.1" diff --git a/codex-rs/tui/Cargo.toml b/codex-rs/tui/Cargo.toml index a6b31b49bb..1b0ed9771e 100644 --- a/codex-rs/tui/Cargo.toml +++ b/codex-rs/tui/Cargo.toml @@ -93,7 +93,8 @@ toml = { workspace = true } tracing = { workspace = true, features = ["log"] } tracing-appender = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } -syntect = { workspace = true } +syntect = "5" +two-face = { version = "0.5", default-features = false, features = ["syntect-default-onig"] } unicode-segmentation = { workspace = true } unicode-width = { workspace = true } url = { workspace = true } diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_modal_exec.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_modal_exec.snap index bf73dc6e93..055a6292f1 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_modal_exec.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__exec_approval_modal_exec.snap @@ -27,8 +27,8 @@ Buffer { x: 73, y: 4, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, x: 2, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: ITALIC, x: 7, y: 5, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, - x: 4, y: 7, fg: Rgb(150, 181, 180), bg: Reset, underline: Reset, modifier: NONE, - x: 8, y: 7, fg: Rgb(192, 197, 206), bg: Reset, underline: Reset, modifier: NONE, + x: 4, y: 7, fg: Rgb(137, 180, 250), bg: Reset, underline: Reset, modifier: NONE, + x: 8, y: 7, fg: Rgb(205, 214, 244), bg: Reset, underline: Reset, modifier: NONE, x: 20, y: 7, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, x: 0, y: 9, fg: Cyan, bg: Reset, underline: Reset, modifier: BOLD, x: 21, y: 9, fg: Reset, bg: Reset, underline: Reset, modifier: NONE, diff --git a/codex-rs/tui/src/render/highlight.rs b/codex-rs/tui/src/render/highlight.rs index f70c714143..1323b4b03d 100644 --- a/codex-rs/tui/src/render/highlight.rs +++ b/codex-rs/tui/src/render/highlight.rs @@ -8,10 +8,10 @@ use syntect::easy::HighlightLines; use syntect::highlighting::FontStyle; use syntect::highlighting::Style as SyntectStyle; use syntect::highlighting::Theme; -use syntect::highlighting::ThemeSet; use syntect::parsing::SyntaxReference; use syntect::parsing::SyntaxSet; use syntect::util::LinesWithEndings; +use two_face::theme::EmbeddedThemeName; // -- Global singletons ------------------------------------------------------- @@ -19,13 +19,18 @@ static SYNTAX_SET: OnceLock = OnceLock::new(); static THEME: OnceLock = OnceLock::new(); fn syntax_set() -> &'static SyntaxSet { - SYNTAX_SET.get_or_init(SyntaxSet::load_defaults_newlines) + SYNTAX_SET.get_or_init(two_face::syntax::extra_newlines) } fn theme() -> &'static Theme { THEME.get_or_init(|| { - let ts = ThemeSet::load_defaults(); - ts.themes["base16-ocean.dark"].clone() + let ts = two_face::theme::extra(); + // Pick light or dark theme based on terminal background color. + let name = match crate::terminal_palette::default_bg() { + Some(bg) if crate::color::is_light(bg) => EmbeddedThemeName::CatppuccinLatte, + _ => EmbeddedThemeName::CatppuccinMocha, + }; + ts.get(name).clone() }) } @@ -77,9 +82,7 @@ fn convert_style(syn_style: SyntectStyle) -> Style { if syn_style.font_style.contains(FontStyle::BOLD) { rt_style.add_modifier |= Modifier::BOLD; } - if syn_style.font_style.contains(FontStyle::ITALIC) { - rt_style.add_modifier |= Modifier::ITALIC; - } + // Intentionally skip italic — many terminals render it poorly or not at all. if syn_style.font_style.contains(FontStyle::UNDERLINE) { rt_style.add_modifier |= Modifier::UNDERLINED; } @@ -313,7 +316,8 @@ mod tests { // Background is intentionally skipped. assert_eq!(rt.bg, None); assert!(rt.add_modifier.contains(Modifier::BOLD)); - assert!(rt.add_modifier.contains(Modifier::ITALIC)); + // Italic is intentionally suppressed. + assert!(!rt.add_modifier.contains(Modifier::ITALIC)); assert!(!rt.add_modifier.contains(Modifier::UNDERLINED)); } @@ -385,12 +389,10 @@ mod tests { #[test] fn find_syntax_resolves_all_canonical_languages() { - // Every canonical name that normalize_lang produces AND that syntect's - // default syntax set supports must resolve. Note: syntect's defaults - // do NOT include TypeScript, TSX, Kotlin, Swift, or Zig, so those are - // intentionally omitted here (they gracefully fall back to plain text). let canonical = [ "javascript", + "typescript", + "tsx", "python", "ruby", "rust", @@ -399,31 +401,25 @@ mod tests { "cpp", "yaml", "bash", + "kotlin", "markdown", "sql", "lua", + "zig", + "swift", "java", ]; for lang in canonical { assert!( find_syntax(lang).is_some(), - "find_syntax({lang:?}) returned None — syntect cannot resolve this canonical name" + "find_syntax({lang:?}) returned None" ); } - // Also verify common raw extensions resolve. - let extensions = ["rs", "py", "js", "rb", "go", "sh", "md", "yml"]; + let extensions = ["rs", "py", "js", "ts", "rb", "go", "sh", "md", "yml"]; for ext in extensions { assert!( find_syntax(ext).is_some(), - "find_syntax({ext:?}) returned None — extension lookup failed" - ); - } - // Unsupported languages should return None (graceful fallback). - let unsupported = ["typescript", "tsx", "kotlin", "swift", "zig"]; - for lang in unsupported { - assert!( - find_syntax(lang).is_none(), - "find_syntax({lang:?}) unexpectedly returned Some — update test if syntect added support" + "find_syntax({ext:?}) returned None" ); } }