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
|
// 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
|
// level, choose the indent size that is the most common between all
|
||||||
// of the lines.
|
// of the lines.
|
||||||
|
@ -7025,7 +7027,7 @@ impl Editor {
|
||||||
|
|
||||||
let indent_size = indent_size_occurrences
|
let indent_size = indent_size_occurrences
|
||||||
.into_iter()
|
.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)
|
.map(|(indent, _)| indent)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let row = rows_by_indent_size[&indent_size][0];
|
let row = rows_by_indent_size[&indent_size][0];
|
||||||
|
@ -7051,6 +7053,10 @@ impl Editor {
|
||||||
should_rewrap = true;
|
should_rewrap = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !should_rewrap {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if selection.is_empty() {
|
if selection.is_empty() {
|
||||||
'expand_upwards: while start_row > 0 {
|
'expand_upwards: while start_row > 0 {
|
||||||
let prev_row = start_row - 1;
|
let prev_row = start_row - 1;
|
||||||
|
@ -7075,10 +7081,6 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !should_rewrap {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let start = Point::new(start_row, 0);
|
let start = Point::new(start_row, 0);
|
||||||
let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
|
let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
|
||||||
let selection_text = buffer.text_for_range(start..end).collect::<String>();
|
let selection_text = buffer.text_for_range(start..end).collect::<String>();
|
||||||
|
@ -7097,29 +7099,15 @@ impl Editor {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let unwrapped_text = lines_without_prefixes.join(" ");
|
|
||||||
let wrap_column = buffer
|
let wrap_column = buffer
|
||||||
.settings_at(Point::new(start_row, 0), cx)
|
.settings_at(Point::new(start_row, 0), cx)
|
||||||
.preferred_line_length as usize;
|
.preferred_line_length as usize;
|
||||||
let mut wrapped_text = String::new();
|
let wrapped_text = wrap_with_prefix(
|
||||||
let mut current_line = line_prefix.clone();
|
line_prefix,
|
||||||
for word in unwrapped_text.split_whitespace() {
|
lines_without_prefixes.join(" "),
|
||||||
if current_line.len() + word.len() >= wrap_column {
|
wrap_column,
|
||||||
wrapped_text.push_str(¤t_line);
|
tab_size,
|
||||||
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 diff = TextDiff::from_lines(&selection_text, &wrapped_text);
|
let diff = TextDiff::from_lines(&selection_text, &wrapped_text);
|
||||||
let mut offset = start.to_offset(&buffer);
|
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(
|
fn hunks_for_selections(
|
||||||
multi_buffer_snapshot: &MultiBufferSnapshot,
|
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||||
selections: &[Selection<Anchor>],
|
selections: &[Selection<Anchor>],
|
||||||
|
|
|
@ -46,6 +46,7 @@ use std::{
|
||||||
future::Future,
|
future::Future,
|
||||||
iter::{self, Iterator, Peekable},
|
iter::{self, Iterator, Peekable},
|
||||||
mem,
|
mem,
|
||||||
|
num::NonZeroU32,
|
||||||
ops::{Deref, DerefMut, Range},
|
ops::{Deref, DerefMut, Range},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
str,
|
str,
|
||||||
|
@ -4325,6 +4326,13 @@ impl IndentSize {
|
||||||
}
|
}
|
||||||
self
|
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"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue