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:
parent
dc02894db4
commit
369de400be
2 changed files with 85 additions and 25 deletions
|
@ -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(¤t_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(¤t_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(¤t_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(¤t_line);
|
||||
}
|
||||
wrapped_text
|
||||
}
|
||||
|
||||
fn hunks_for_selections(
|
||||
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||
selections: &[Selection<Anchor>],
|
||||
|
|
|
@ -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"))]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue