diff --git a/codex-rs/tui/src/render/highlight.rs b/codex-rs/tui/src/render/highlight.rs index e6d200cc3d..867d111f31 100644 --- a/codex-rs/tui/src/render/highlight.rs +++ b/codex-rs/tui/src/render/highlight.rs @@ -1,6 +1,7 @@ +use ratatui::text::Line; + use ratatui::style::Style; use ratatui::style::Stylize; -use ratatui::text::Line; use ratatui::text::Span; use std::sync::OnceLock; use tree_sitter_highlight::Highlight; @@ -105,15 +106,31 @@ fn push_segment(lines: &mut Vec>, segment: &str, style: Option Vec> { + if script.is_empty() { + vec![Line::from("")] + } else { + script + .split('\n') + .map(|s| Line::from(s.to_string())) + .collect() + } +} + /// Convert a bash script into per-line styled content using tree-sitter's /// bash highlight query. The highlighter is streamed so multi-line content is /// split into `Line`s while preserving style boundaries. pub(crate) fn highlight_bash_to_lines(script: &str) -> Vec> { + // Runtime gate: on Windows, skip bash-specific highlighting and return plain text. + if cfg!(target_os = "windows") { + return plain_text_lines(script); + } + let mut highlighter = Highlighter::new(); let iterator = match highlighter.highlight(highlight_config(), script.as_bytes(), None, |_| None) { Ok(iter) => iter, - Err(_) => return vec![script.to_string().into()], + Err(_) => return plain_text_lines(script), }; let mut lines: Vec> = vec![Line::from("")]; @@ -132,7 +149,7 @@ pub(crate) fn highlight_bash_to_lines(script: &str) -> Vec> { let style = highlight_stack.last().map(|h| highlight_for(*h).style()); push_segment(&mut lines, &script[start..end], style); } - Err(_) => return vec![script.to_string().into()], + Err(_) => return plain_text_lines(script), } } @@ -145,10 +162,14 @@ pub(crate) fn highlight_bash_to_lines(script: &str) -> Vec> { #[cfg(test)] mod tests { + #[cfg(not(target_os = "windows"))] use super::*; + #[cfg(not(target_os = "windows"))] use pretty_assertions::assert_eq; + #[cfg(not(target_os = "windows"))] use ratatui::style::Modifier; + #[cfg(not(target_os = "windows"))] fn reconstructed(lines: &[Line<'static>]) -> String { lines .iter() @@ -162,6 +183,7 @@ mod tests { .join("\n") } + #[cfg(not(target_os = "windows"))] fn dimmed_tokens(lines: &[Line<'static>]) -> Vec { lines .iter() @@ -173,6 +195,7 @@ mod tests { .collect() } + #[cfg(not(target_os = "windows"))] #[test] fn dims_expected_bash_operators() { let s = "echo foo && bar || baz | qux & (echo hi)"; @@ -185,6 +208,7 @@ mod tests { assert!(!dimmed.contains(&"echo".to_string())); } + #[cfg(not(target_os = "windows"))] #[test] fn dims_redirects_and_strings() { let s = "echo \"hi\" > out.txt; echo 'ok'"; @@ -197,6 +221,7 @@ mod tests { assert!(dimmed.contains(&"'ok'".to_string())); } + #[cfg(not(target_os = "windows"))] #[test] fn highlights_command_and_strings() { let s = "echo \"hi\""; @@ -219,6 +244,7 @@ mod tests { assert!(string_style.add_modifier.contains(Modifier::DIM)); } + #[cfg(not(target_os = "windows"))] #[test] fn highlights_heredoc_body_as_string() { let s = "cat <