There's a weird rendering issue with transcript mode: Tab chars bleed through when scrolling up/down. e.g. `nl -ba ...` adds tab chars to each line, which make scrolling look glitchy in transcript mode. Before: https://github.com/user-attachments/assets/631ee7fc-6083-4d35-aaf0-a0b08e734470 After: https://github.com/user-attachments/assets/bbba6111-4bfc-4862-8357-0f51aa2a21ac
59 lines
2.1 KiB
Rust
59 lines
2.1 KiB
Rust
use ansi_to_tui::Error;
|
|
use ansi_to_tui::IntoText;
|
|
use ratatui::text::Line;
|
|
use ratatui::text::Text;
|
|
|
|
// Expand tabs in a best-effort way for transcript rendering.
|
|
// Tabs can interact poorly with left-gutter prefixes in our TUI and CLI
|
|
// transcript views (e.g., `nl` separates line numbers from content with a tab).
|
|
// Replacing tabs with spaces avoids odd visual artifacts without changing
|
|
// semantics for our use cases.
|
|
fn expand_tabs(s: &str) -> std::borrow::Cow<'_, str> {
|
|
if s.contains('\t') {
|
|
// Keep it simple: replace each tab with 4 spaces.
|
|
// We do not try to align to tab stops since most usages (like `nl`)
|
|
// look acceptable with a fixed substitution and this avoids stateful math
|
|
// across spans.
|
|
std::borrow::Cow::Owned(s.replace('\t', " "))
|
|
} else {
|
|
std::borrow::Cow::Borrowed(s)
|
|
}
|
|
}
|
|
|
|
/// This function should be used when the contents of `s` are expected to match
|
|
/// a single line. If multiple lines are found, a warning is logged and only the
|
|
/// first line is returned.
|
|
pub fn ansi_escape_line(s: &str) -> Line<'static> {
|
|
// Normalize tabs to spaces to avoid odd gutter collisions in transcript mode.
|
|
let s = expand_tabs(s);
|
|
let text = ansi_escape(&s);
|
|
match text.lines.as_slice() {
|
|
[] => "".into(),
|
|
[only] => only.clone(),
|
|
[first, rest @ ..] => {
|
|
tracing::warn!("ansi_escape_line: expected a single line, got {first:?} and {rest:?}");
|
|
first.clone()
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn ansi_escape(s: &str) -> Text<'static> {
|
|
// to_text() claims to be faster, but introduces complex lifetime issues
|
|
// such that it's not worth it.
|
|
match s.into_text() {
|
|
Ok(text) => text,
|
|
Err(err) => match err {
|
|
Error::NomError(message) => {
|
|
tracing::error!(
|
|
"ansi_to_tui NomError docs claim should never happen when parsing `{s}`: {message}"
|
|
);
|
|
panic!();
|
|
}
|
|
Error::Utf8Error(utf8error) => {
|
|
tracing::error!("Utf8Error: {utf8error}");
|
|
panic!();
|
|
}
|
|
},
|
|
}
|
|
}
|