mirror of
https://github.com/openai/codex.git
synced 2026-04-24 06:35:50 +00:00
feat(tui): use two-face for extended syntax highlighting and adaptive theme
Replace syntect's default ~40 language set with two-face's bat-sourced ~250 language set, adding support for TypeScript, TSX, Kotlin, Swift, and Zig. Auto-detect terminal background to pick CatppuccinMocha (dark) or CatppuccinLatte (light). Suppress italic modifier by default since many terminals render it poorly.
This commit is contained in:
12
codex-rs/Cargo.lock
generated
12
codex-rs/Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<SyntaxSet> = OnceLock::new();
|
||||
static THEME: OnceLock<Theme> = 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user