diff --git a/src/util.rs b/src/util.rs index e560ba7e1..d3672eee3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -202,6 +202,18 @@ pub fn truncate(s: &'_ str, width: usize) -> Cow<'_, str> { } } +/// Trim trailing whitespace from a string that may contain ANSI escape sequences. +/// Unlike str::trim_end(), this correctly handles ANSI codes at the end of the string +/// that would otherwise prevent trimming of trailing whitespace. +pub fn ansi_trim_end(s: &str) -> String { + let stripped = console::strip_ansi_codes(s); + let trimmed_width = UnicodeWidthStr::width(stripped.trim_end()); + if trimmed_width == UnicodeWidthStr::width(stripped.as_ref()) { + return s.to_string(); + } + truncate(s, trimmed_width).into_owned() +} + pub fn find_column_kind(pat: &str) -> Option { // strict search at first for (k, (v, _)) in KIND_LIST.iter() { diff --git a/src/view.rs b/src/view.rs index 6fd9571f4..a31da5a2d 100644 --- a/src/view.rs +++ b/src/view.rs @@ -6,7 +6,9 @@ use crate::opt::{ArgColorMode, ArgPagerMode}; use crate::process::collect_proc; use crate::style::{apply_color, apply_style, color_to_column_style}; use crate::term_info::TermInfo; -use crate::util::{KeywordClass, classify, find_column_kind, find_exact, find_partial, truncate}; +use crate::util::{ + KeywordClass, ansi_trim_end, classify, find_column_kind, find_exact, find_partial, truncate, +}; use anyhow::{Error, bail}; #[cfg(not(target_os = "windows"))] use pager::Pager; @@ -511,7 +513,7 @@ impl View { ); } } - row = row.trim_end().to_string(); + row = ansi_trim_end(&row); row = truncate(&row, self.term_info.width).to_string(); self.term_info.write_line(&row)?; Ok(()) @@ -533,7 +535,7 @@ impl View { ); } } - row = row.trim_end().to_string(); + row = ansi_trim_end(&row); row = truncate(&row, self.term_info.width).to_string(); self.term_info.write_line(&row)?; Ok(()) @@ -562,7 +564,7 @@ impl View { ); } } - row = row.trim_end().to_string(); + row = ansi_trim_end(&row); row = truncate(&row, self.term_info.width).to_string(); self.term_info.write_line(&row)?; Ok(())