Fix garbled ANSI output when using bat as MANPAGER (--strip-ansi=auto now preserves man formatting)#3792
Open
Anntoin wants to merge 9 commits into
Open
Fix garbled ANSI output when using bat as MANPAGER (--strip-ansi=auto now preserves man formatting)#3792Anntoin wants to merge 9 commits into
Anntoin wants to merge 9 commits into
Conversation
Anntoin
added a commit
to Anntoin/bat
that referenced
this pull request
Jun 3, 2026
…kdp#3792) Signed-off-by: Anntóin Wilkinson <anntoin@gmail.com>
Anntoin
added a commit
to Anntoin/bat
that referenced
this pull request
Jun 3, 2026
…kdp#3792) Signed-off-by: Anntóin Wilkinson <anntoin@gmail.com>
835835c to
567f54b
Compare
AnsiStyle and Attributes need Clone and Default implementations so that the overlay approach can clone style snapshots and create default (empty) instances. These derives are a prerequisite for strip_ansi_with_overlay() which builds a Vec<(usize, AnsiStyle)> mapping. Signed-off-by: Anntóin Wilkinson <anntoin@gmail.com>
Add strip_ansi_with_overlay() which strips ANSI escape sequences from a line and returns both the stripped text and a style overlay mapping byte positions to the AnsiStyle active at each position in the original text. This allows syntax highlighting (syntect) to operate on clean text, while preserving original ANSI formatting (bold, underline, color, hyperlinks) for re-application during rendering. Includes 20 unit tests covering bold, underline, dim, italic, foreground and background colors (8-bit, 256-color, truecolor), SGR reset, OSC8 hyperlinks, combined styles, adjacent style changes, and edge cases. Update StripAnsiMode doc comment to document the new Auto mode semantics. Mark strip_ansi() with #[allow(dead_code)] as it is no longer called outside of tests. Signed-off-by: Anntóin Wilkinson <anntoin@gmail.com>
Wire strip_ansi_with_overlay() into the InteractivePrinter so that when strip_ansi is active, the ANSI style overlay is computed for each line and used to determine the correct style for each syntax-highlighted region. The three-way region_style logic: - reapply_ansi_overlay (StripAnsiMode::Auto + colored output): look up the overlay style at the region's byte offset, so original ANSI formatting (bold, underline, hyperlinks) is layered on top of syntect colors. - strip_ansi without overlay (StripAnsiMode::Always): use a default/empty style, discarding all ANSI formatting. - no strip_ansi: use the live mutable ansi_style so escape sequences within a region are reflected in subsequent text chunks. Add ansi_style_overlay and reapply_ansi_overlay fields to InteractivePrinter. Signed-off-by: Anntóin Wilkinson <anntoin@gmail.com>
Update the long-help text for --strip-ansi to explain that 'auto' mode strips ANSI before syntax highlighting but re-applies semantic formatting (bold, underline, hyperlinks) on the output so both highlighting and input formatting are preserved. Note that this is the recommended mode for use as MANPAGER. Signed-off-by: Anntóin Wilkinson <anntoin@gmail.com>
…ototype Add ANSI and OSC escape sequence patterns to the prototype context in Manpage.sublime-syntax so they are consumed before syntax highlighting rules match against them. This prevents escape sequences from breaking across highlight regions when using --strip-ansi=auto with man pages. Signed-off-by: Anntóin Wilkinson <anntoin@gmail.com>
…kdp#3792) Signed-off-by: Anntóin Wilkinson <anntoin@gmail.com>
- Add #[cfg(test)] to test-only overlay_style_at helper in preprocessor.rs - Derive PartialEq on AnsiStyle/Attributes to replace format!()-based comparison with direct equality check (simpler, zero-allocation) - Replace map_or(true, |(_, prev)| format!(...) != format!(...)) with map_or(true, |(_, prev)| *prev != current_style) - Extract region_ansi_style() method from duplicated get_region_style closures in both colored and uncolored printer branches - Remove 36 lines of duplicated closure+comment boilerplate Signed-off-by: Anntóin Wilkinson <anntoin@gmail.com>
262c2a2 to
8cb6c21
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When using
batasMANPAGER, ANSI SGR reset codes like\x1B[22m(reset bold) appear as literal text "22m" in the output. This happens because the Manpage sublime-syntax definition does not consume ANSI escapes broadly enough, so syntect splits escape sequences across highlight regions.Even with a syntax fix,
^-anchored heading patterns like^(NAME|SYNOPSIS|...)cannot match after an ANSI escape is consumed at position 0, meaning syntax highlighting of headings is fundamentally incompatible with ANSI passthrough.Fixes #3724.
Solution: ANSI Overlay
This PR introduces a "strip-and-reapply" overlay mechanism for
--strip-ansi=auto:AnsiStyleoverlay that captures all formatting state (bold, underline, colors, OSC8 hyperlinks)Example
This produces output with:
NAME,SYNOPSIS, etc.)\x1B[22mvisible textMode behavior
--strip-ansinever(default)autoalwaysCommits
strip_ansi_with_overlay()to preprocessor — Core overlay function with 20 unit testsregion_stylelogic--strip-ansihelp text for auto mode and MANPAGER usage — Documents the new behaviorPerformance
Negligible overhead. Benchmarked with 5 man pages (70K–2501K):
neverautoThe largest page shows no measurable overhead. For medium pages, overhead is 14–20% (< 10ms absolute).
Test coverage
strip_ansi_with_overlay()covering: bold, underline, dim, italic, foreground/background color, 256-color, truecolor, SGR reset, OSC8 hyperlinks (open, close, combined with bold), adjacent style changes, empty input, escapes-only inputno_duplicate_extensionsfailure (unrelated, from syntax set)Note on PR #3762
This PR includes commit
9e0c2a9dfrom #3762 (thepop_visible_charfix for overstrike stripping). That PR is independently valuable and should be merged regardless of this PR. If #3762 is merged first, this PR will need a rebase to drop the cherry-picked commit.