1
0
mirror of https://github.com/sharkdp/bat synced 2026-07-29 18:01:43 +00:00

Add word wrapping mode (#3597)

* feat: add word wrapping mode for --wrap flag

* Run `cargo fmt` and add CHANGELOG entry

* Add word wrap tests, update manpage and shell completions

- Add integration tests for word wrapping: basic word boundary breaking,
  fallback to character wrapping for long words, line numbers, and
  short lines that fit without wrapping
- Update manpage to document the new 'word' wrapping mode
- Update bash, fish, zsh, and PowerShell completions with 'word' option
- Avoid unnecessary clone of `line_buf` when word wrap is disabled

* make clippy and cargo fmt happy

---------

Co-authored-by: Keith Hall <keith-hall@users.noreply.github.com>
This commit is contained in:
Varun Chawla
2026-03-02 19:18:49 -08:00
committed by GitHub
parent 1424f9d6bf
commit cc5f782d28
14 changed files with 130 additions and 15 deletions
+1
View File
@@ -405,6 +405,7 @@ impl App {
} else {
match self.matches.get_one::<String>("wrap").map(|s| s.as_str()) {
Some("character") => WrappingMode::Character,
Some("word") => WrappingMode::Word,
Some("never") => WrappingMode::NoWrapping(true),
Some("auto") | None => {
if self.interactive_output || maybe_term_width.is_some() {
+3 -3
View File
@@ -211,11 +211,11 @@ pub fn build_app(interactive_output: bool) -> Command {
.long("wrap")
.overrides_with("wrap")
.value_name("mode")
.value_parser(["auto", "never", "character"])
.value_parser(["auto", "never", "character", "word"])
.default_value("auto")
.hide_default_value(true)
.help("Specify the text-wrapping mode (*auto*, never, character).")
.long_help("Specify the text-wrapping mode (*auto*, never, character). \
.help("Specify the text-wrapping mode (*auto*, never, character, word).")
.long_help("Specify the text-wrapping mode (*auto*, never, character, word). \
The '--terminal-width' option can be used in addition to \
control the output width."),
)
+50 -3
View File
@@ -781,11 +781,21 @@ impl Printer for InteractivePrinter<'_> {
// Displayed width of line_buf
let mut current_width = 0;
let word_wrap = matches!(self.config.wrapping_mode, WrappingMode::Word);
// For word wrapping, track last whitespace position.
let mut last_ws_idx: Option<usize> = None;
for c in text.chars() {
// calculate the displayed width for next character
let cw = c.width().unwrap_or(0);
current_width += cw;
// Track whitespace positions for word wrapping.
if word_wrap && c.is_whitespace() {
last_ws_idx = Some(line_buf.len());
}
// if next character cannot be printed on this line,
// flush the buffer.
if current_width > max_width {
@@ -807,13 +817,37 @@ impl Printer for InteractivePrinter<'_> {
}
}
// Determine the break point and remainder
// for word wrapping.
let (emit_end, rest_start) = if word_wrap {
if let Some(ws_idx) = last_ws_idx {
// Skip the whitespace character itself
// and carry the rest to the next line.
let rs = ws_idx
+ line_buf[ws_idx..]
.chars()
.next()
.map(|ch| ch.len_utf8())
.unwrap_or(0);
(ws_idx, Some(rs))
} else {
(line_buf.len(), None)
}
} else {
(line_buf.len(), None)
};
// It wraps.
write!(
handle,
"{}{}\n{}",
as_terminal_escaped(
style,
&format!("{}{line_buf}", self.ansi_style),
&format!(
"{}{}",
self.ansi_style,
&line_buf[..emit_end]
),
self.config.true_color,
self.config.colored_output,
self.config.use_italic_text,
@@ -826,8 +860,21 @@ impl Printer for InteractivePrinter<'_> {
cursor = 0;
max_width = cursor_max;
line_buf.clear();
current_width = cw;
if let Some(rs) = rest_start {
// Word wrap: carry remainder to next line.
let remainder = line_buf[rs..].to_string();
let rem_width: usize = remainder
.chars()
.map(|ch| ch.width().unwrap_or(0))
.sum();
line_buf.clear();
line_buf.push_str(&remainder);
current_width = rem_width + cw;
} else {
line_buf.clear();
current_width = cw;
}
last_ws_idx = None;
}
line_buf.push(c);
+1
View File
@@ -1,6 +1,7 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WrappingMode {
Character,
Word,
// The bool specifies whether wrapping has been explicitly disabled by the user via --wrap=never
NoWrapping(bool),
}