From bb7d47c767ce021a95dd36c36ed7754f221d8a5e Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Sun, 22 Feb 2026 20:22:07 -0800 Subject: [PATCH 1/2] Fix double-spaced TTY output caused by ANSI-unaware trim_end When outputting to a terminal, each row's trailing whitespace was not properly trimmed because str::trim_end() cannot see past ANSI escape sequences (e.g. color reset codes) at the end of the string. This left rows padded to their full column width, causing them to fill the entire terminal line. When the terminal auto-wraps at the right margin and write_line then appends a newline, an extra blank line appears between every row. Replace trim_end() with ansi_trim_end() which strips ANSI codes to measure the actual trimmed width, then truncates the original styled string to that width. Fixes #848 --- src/util.rs | 13 +++++++++++++ src/view.rs | 8 ++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/util.rs b/src/util.rs index e560ba7e1..927ba53a9 100644 --- a/src/util.rs +++ b/src/util.rs @@ -202,6 +202,19 @@ 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..1d76d73f5 100644 --- a/src/view.rs +++ b/src/view.rs @@ -6,7 +6,7 @@ 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 +511,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 +533,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 +562,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(()) From 11b82762d8c9e77f59e215641b6d6b9e388f5112 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Mon, 23 Feb 2026 23:32:29 -0800 Subject: [PATCH 2/2] Fix rustfmt formatting --- src/util.rs | 1 - src/view.rs | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/util.rs b/src/util.rs index 927ba53a9..d3672eee3 100644 --- a/src/util.rs +++ b/src/util.rs @@ -214,7 +214,6 @@ pub fn ansi_trim_end(s: &str) -> 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 1d76d73f5..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, ansi_trim_end, 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;