Repository navigation
Conversation
|
Hi sharkdp — #4018 has been open since 25 September with CI green (24 checks) and I have not had a reply yet, so this is the one nudge I planned. The short version: Reproduced on Two things I could check from here if either is the blocker: whether you would rather this went through an issue first (CONTRIBUTING only asks for that on new features, so I read a tab-expansion fix as exempt), and whether Happy to close it if you would rather handle this one yourself. |
MohammediYunus
left a comment
There was a problem hiding this comment.
Thanks for fixing this! I checked the CLI output at ea8c9ac against 4987f76. With tabs set to 4, the CJK and decomposed-accent cases below now reach the correct tab stop. Both were short on the base revision.
This small integration test complements the unit cases by exercising the printer path. It fails on the base and passes on this head, with no added dependency:
#[test]
fn tabs_4_unicode_columns() {
bat()
.arg("--tabs=4")
.arg("--style=plain")
.arg("--decorations=always")
.arg("--color=never")
.arg("--paging=never")
.arg("--wrap=never")
.write_stdin("界\tz\ne\u{301}\tz\n")
.assert()
.success()
.stdout("界 z\ne\u{301} z\n");
}I also checked highlighted lines with TwoDark, a 20-column terminal width, and wrapping disabled. ASCII LF output stays at 20 columns; CRLF improves from 19 to 20. The tabs-disabled base/head comparisons in the same small input matrix were byte-identical.
Validation: macOS arm64, Rust 1.97.1, locked dependencies with minimal-application features. The added integration test was run with:
cargo test --locked --no-default-features --features minimal-application \
--test integration_tests tabs_4_unicode_columns -- --exact|
Thanks for checking the CLI output and for writing that test — it goes through the printer end to end, which my unit tests in Added it unchanged in |
expand_tabs advances its cursor with the byte offset of a tab found by str::find and with str::len for the remainder, but the cursor is a column counter: it is compared against the terminal width to pad each line, and the stop itself is derived from it as width - cursor % width. Any multi-byte character before a tab therefore inflated the cursor by len_utf8 - width and every later tab stop landed early. "🦀\t" with --tabs=4 is four bytes and two columns, so the tab expanded to nothing and the line came out two columns short, which shifts the --style=grid column. printer.rs already measures characters with UnicodeWidthChar for exactly this reason; use the same crate here.
8f8efd9 to
e647283
Compare
|
Rebased onto Head is now Verified on the rebased head: |
Description
expand_tabsadvances itscursorwith a byte offset and a byte length, butcursoris a terminal-column counter.str::findreturns a byte offset andstr::lenis a byte count, so any multi-byte character before a tab inflatescursorbylen_utf8 - width, and every later tab stop lands early.cursoriscursor_totalinprinter.rs, and it decides how much padding each line gets:so the emitted line is short by exactly the byte/column discrepancy and the
--style=grid,numberscolumn shifts.Reproduction
masterat4987f76709aae3a1c4db723c53874c9ddcb0c4fd,--tabs=4:é\té(1 space)é(3 spaces)€\t€€🦀\t🦀(no spaces at all)🦀a\t🦀\ta 🦀a 🦀🦀is four bytes and two columns, so onmasterthe tab expands to nothing and the line ends up two columns short.printer.rsalready has the correct measurement for this quantity and explains why:expand_tabswas not using it.Fix
Measure with
UnicodeWidthStr::widthfrom theunicode-widthcrate bat already depends on, in the two places that advanced the cursor by a byte count.Test plan
Three tests added to
src/preprocessor.rs, next to the existing inline#[test]functions, using the file's existing style. They cover a multi-byte character with no tab, the tab stop after one and two columns of multi-byte text, and multi-byte text between two tabs, which exercises thetext.len()line. Each pins an ASCII control case alongside it, so the tab-stop arithmetic itself cannot be broken while making the multi-byte cases pass.Before the fix, with only the tests added:
After:
The existing tab tests all read
tests/examples/tabs.txt, which is pure ASCII, so they pass both before and after — they were never going to catch this.Toolchain:
1.98.0, macOS.