From aee49f1e10968d2efd3f92bffe810d5e0abee991 Mon Sep 17 00:00:00 2001 From: mattsu <35655889+mattsu2020@users.noreply.github.com> Date: Wed, 28 Jan 2026 23:17:08 +0900 Subject: [PATCH] Add support for -c short option and fix character mode tab handling (#10530) This commit adds the `-c` short option for the `--characters` flag and fixes tab handling in character mode. Previously, tabs were only handled in column mode, but now they are properly processed in both character and column modes. The character mode now correctly advances to the next tab stop, handles carriage returns, and backspace characters. Tests have been added to verify the new functionality. --- src/uu/fold/src/fold.rs | 33 ++++++++++++++++++++++++++++----- tests/by-util/test_fold.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index d79d6d422..71d4756a7 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -98,6 +98,7 @@ pub fn uu_app() -> Command { .arg( Arg::new(options::CHARACTERS) .long(options::CHARACTERS) + .short('c') .help(translate!("fold-characters-help")) .conflicts_with(options::BYTES) .action(ArgAction::SetTrue), @@ -260,9 +261,31 @@ fn next_tab_stop(col_count: usize) -> usize { fn compute_col_count(buffer: &[u8], mode: WidthMode) -> usize { match mode { - WidthMode::Characters => std::str::from_utf8(buffer) - .map(|s| s.chars().count()) - .unwrap_or(buffer.len()), + WidthMode::Characters => { + if let Ok(s) = std::str::from_utf8(buffer) { + let mut width = 0; + for ch in s.chars() { + match ch { + '\r' => width = 0, + '\t' => width = next_tab_stop(width), + '\x08' => width = width.saturating_sub(1), + _ => width += 1, + } + } + width + } else { + let mut width = 0; + for &byte in buffer { + match byte { + CR => width = 0, + TAB => width = next_tab_stop(width), + 0x08 => width = width.saturating_sub(1), + _ => width += 1, + } + } + width + } + } WidthMode::Columns => { if let Ok(s) = std::str::from_utf8(buffer) { let mut width = 0; @@ -382,7 +405,7 @@ fn process_ascii_line(line: &[u8], ctx: &mut FoldContext<'_, W>) -> UR *ctx.col_count = ctx.col_count.saturating_sub(1); idx += 1; } - TAB if ctx.mode == WidthMode::Columns => { + TAB => { loop { let next_stop = next_tab_stop(*ctx.col_count); if next_stop > ctx.width && !ctx.output.is_empty() { @@ -523,7 +546,7 @@ fn process_utf8_chars(line: &str, ctx: &mut FoldContext<'_, W>) -> URe continue; } - if ctx.mode == WidthMode::Columns && ch == '\t' { + if ch == '\t' { loop { let next_stop = next_tab_stop(*ctx.col_count); if next_stop > ctx.width && !ctx.output.is_empty() { diff --git a/tests/by-util/test_fold.rs b/tests/by-util/test_fold.rs index 1fe466ba5..c6ae6b56d 100644 --- a/tests/by-util/test_fold.rs +++ b/tests/by-util/test_fold.rs @@ -65,6 +65,15 @@ fn test_wide_characters_with_characters_option() { .stdout_is("\u{B250}\u{B250}\u{B250}\n"); } +#[test] +fn test_wide_characters_with_characters_short_option() { + new_ucmd!() + .args(&["-c", "-w", "5"]) + .pipe_in("\u{B250}\u{B250}\u{B250}\n") + .succeeds() + .stdout_is("\u{B250}\u{B250}\u{B250}\n"); +} + #[test] fn test_multiple_wide_characters_in_column_mode() { let wide = '\u{FF1A}'; @@ -540,6 +549,24 @@ fn test_fold_after_tab() { .stdout_is("a\tbb\nb\n"); } +#[test] +fn test_fold_characters_tab_advances_to_next_tab_stop() { + new_ucmd!() + .args(&["-c", "-w", "4"]) + .pipe_in("ab\tcd\n") + .succeeds() + .stdout_is("ab\n\t\ncd\n"); +} + +#[test] +fn test_fold_characters_tab_with_non_ascii() { + new_ucmd!() + .args(&["-c", "-w", "2"]) + .pipe_in("\u{00E9}\tb\n") + .succeeds() + .stdout_is("\u{00E9}\n\t\nb\n"); +} + #[test] fn test_fold_at_tab_as_word_boundary() { new_ucmd!()