Make rewrapping take tabs more into account (#20196)

Closes #18686


https://github.com/user-attachments/assets/e87b4508-3570-4395-92b4-c5e0e9e19623


Release Notes:

- The Rewrap command now considers the width of each tab character at
the beginning of the line to be the configured tab size.

---------

Co-authored-by: Will <will@zed.dev>
This commit is contained in:
Richard Feldman 2024-11-04 18:10:40 -05:00 committed by GitHub
parent dc02894db4
commit 369de400be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 85 additions and 25 deletions

View file

@ -7008,6 +7008,8 @@ impl Editor {
}
}
let tab_size = buffer.settings_at(selection.head(), cx).tab_size;
// Since not all lines in the selection may be at the same indent
// level, choose the indent size that is the most common between all
// of the lines.
@ -7025,7 +7027,7 @@ impl Editor {
let indent_size = indent_size_occurrences
.into_iter()
.max_by_key(|(indent, count)| (*count, indent.len))
.max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
.map(|(indent, _)| indent)
.unwrap_or_default();
let row = rows_by_indent_size[&indent_size][0];
@ -7051,6 +7053,10 @@ impl Editor {
should_rewrap = true;
}
if !should_rewrap {
continue;
}
if selection.is_empty() {
'expand_upwards: while start_row > 0 {
let prev_row = start_row - 1;
@ -7075,10 +7081,6 @@ impl Editor {
}
}
if !should_rewrap {
continue;
}
let start = Point::new(start_row, 0);
let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
let selection_text = buffer.text_for_range(start..end).collect::<String>();
@ -7097,29 +7099,15 @@ impl Editor {
continue;
};
let unwrapped_text = lines_without_prefixes.join(" ");
let wrap_column = buffer
.settings_at(Point::new(start_row, 0), cx)
.preferred_line_length as usize;
let mut wrapped_text = String::new();
let mut current_line = line_prefix.clone();
for word in unwrapped_text.split_whitespace() {
if current_line.len() + word.len() >= wrap_column {
wrapped_text.push_str(&current_line);
wrapped_text.push('\n');
current_line.truncate(line_prefix.len());
}
if current_line.len() > line_prefix.len() {
current_line.push(' ');
}
current_line.push_str(word);
}
if !current_line.is_empty() {
wrapped_text.push_str(&current_line);
}
let wrapped_text = wrap_with_prefix(
line_prefix,
lines_without_prefixes.join(" "),
wrap_column,
tab_size,
);
let diff = TextDiff::from_lines(&selection_text, &wrapped_text);
let mut offset = start.to_offset(&buffer);
@ -13056,6 +13044,70 @@ impl Editor {
}
}
fn len_with_expanded_tabs(offset: usize, comment_prefix: &str, tab_size: NonZeroU32) -> usize {
let tab_size = tab_size.get() as usize;
let mut width = offset;
for c in comment_prefix.chars() {
width += if c == '\t' {
tab_size - (width % tab_size)
} else {
1
};
}
width - offset
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_size_with_expanded_tabs() {
let nz = |val| NonZeroU32::new(val).unwrap();
assert_eq!(len_with_expanded_tabs(0, "", nz(4)), 0);
assert_eq!(len_with_expanded_tabs(0, "hello", nz(4)), 5);
assert_eq!(len_with_expanded_tabs(0, "\thello", nz(4)), 9);
assert_eq!(len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
assert_eq!(len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
assert_eq!(len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
assert_eq!(len_with_expanded_tabs(0, "x\t", nz(8)), 8);
assert_eq!(len_with_expanded_tabs(7, "x\t", nz(8)), 9);
}
}
fn wrap_with_prefix(
line_prefix: String,
unwrapped_text: String,
wrap_column: usize,
tab_size: NonZeroU32,
) -> String {
let line_prefix_display_len = len_with_expanded_tabs(0, &line_prefix, tab_size);
let mut wrapped_text = String::new();
let mut current_line = line_prefix.to_string();
let prefix_extra_chars = line_prefix_display_len - line_prefix.len();
for word in unwrapped_text.split_whitespace() {
if current_line.len() + prefix_extra_chars + word.len() >= wrap_column {
wrapped_text.push_str(&current_line);
wrapped_text.push('\n');
current_line.truncate(line_prefix.len());
}
if current_line.len() > line_prefix.len() {
current_line.push(' ');
}
current_line.push_str(word);
}
if !current_line.is_empty() {
wrapped_text.push_str(&current_line);
}
wrapped_text
}
fn hunks_for_selections(
multi_buffer_snapshot: &MultiBufferSnapshot,
selections: &[Selection<Anchor>],

View file

@ -46,6 +46,7 @@ use std::{
future::Future,
iter::{self, Iterator, Peekable},
mem,
num::NonZeroU32,
ops::{Deref, DerefMut, Range},
path::{Path, PathBuf},
str,
@ -4325,6 +4326,13 @@ impl IndentSize {
}
self
}
pub fn len_with_expanded_tabs(&self, tab_size: NonZeroU32) -> usize {
match self.kind {
IndentKind::Space => self.len as usize,
IndentKind::Tab => self.len as usize * tab_size.get() as usize,
}
}
}
#[cfg(any(test, feature = "test-support"))]