@@ -1101,6 +1101,12 @@ pub enum MultibufferSelectionMode {
All,
}
+#[derive(Clone, Copy, Debug, Default)]
+pub struct RewrapOptions {
+ pub override_language_settings: bool,
+ pub preserve_existing_whitespace: bool,
+}
+
impl Editor {
pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
let buffer = cx.new(|cx| Buffer::local("", cx));
@@ -3246,7 +3252,13 @@ impl Editor {
.line_len(MultiBufferRow(latest.start.row))
== latest.start.column
{
- this.rewrap_impl(true, cx)
+ this.rewrap_impl(
+ RewrapOptions {
+ override_language_settings: true,
+ preserve_existing_whitespace: true,
+ },
+ cx,
+ )
}
}
this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
@@ -9172,10 +9184,10 @@ impl Editor {
}
pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
- self.rewrap_impl(false, cx)
+ self.rewrap_impl(RewrapOptions::default(), cx)
}
- pub fn rewrap_impl(&mut self, override_language_settings: bool, cx: &mut Context<Self>) {
+ pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
let buffer = self.buffer.read(cx).snapshot(cx);
let selections = self.selections.all::<Point>(cx);
let mut selections = selections.iter().peekable();
@@ -9249,7 +9261,7 @@ impl Editor {
RewrapBehavior::Anywhere => true,
};
- let should_rewrap = override_language_settings
+ let should_rewrap = options.override_language_settings
|| allow_rewrap_based_on_language
|| self.hard_wrap.is_some();
if !should_rewrap {
@@ -9306,15 +9318,16 @@ impl Editor {
});
let wrapped_text = wrap_with_prefix(
line_prefix,
- lines_without_prefixes.join(" "),
+ lines_without_prefixes.join("\n"),
wrap_column,
tab_size,
+ options.preserve_existing_whitespace,
);
// TODO: should always use char-based diff while still supporting cursor behavior that
// matches vim.
let mut diff_options = DiffOptions::default();
- if override_language_settings {
+ if options.override_language_settings {
diff_options.max_word_diff_len = 0;
diff_options.max_word_diff_line_count = 0;
} else {
@@ -17280,10 +17293,10 @@ fn should_stay_with_preceding_ideograph(text: &str) -> bool {
}
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
-struct WordBreakToken<'a> {
- token: &'a str,
- grapheme_len: usize,
- is_whitespace: bool,
+enum WordBreakToken<'a> {
+ Word { token: &'a str, grapheme_len: usize },
+ InlineWhitespace { token: &'a str, grapheme_len: usize },
+ Newline,
}
impl<'a> Iterator for WordBreakingTokenizer<'a> {
@@ -17299,16 +17312,17 @@ impl<'a> Iterator for WordBreakingTokenizer<'a> {
let mut iter = self.input.graphemes(true).peekable();
let mut offset = 0;
- let mut graphemes = 0;
+ let mut grapheme_len = 0;
if let Some(first_grapheme) = iter.next() {
+ let is_newline = first_grapheme == "\n";
let is_whitespace = is_grapheme_whitespace(first_grapheme);
offset += first_grapheme.len();
- graphemes += 1;
+ grapheme_len += 1;
if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
if let Some(grapheme) = iter.peek().copied() {
if should_stay_with_preceding_ideograph(grapheme) {
offset += grapheme.len();
- graphemes += 1;
+ grapheme_len += 1;
}
}
} else {
@@ -17321,27 +17335,29 @@ impl<'a> Iterator for WordBreakingTokenizer<'a> {
if next_word_bound.map_or(false, |(i, _)| i == offset) {
break;
};
- if is_grapheme_whitespace(grapheme) != is_whitespace {
+ if is_grapheme_whitespace(grapheme) != is_whitespace
+ || (grapheme == "\n") != is_newline
+ {
break;
};
offset += grapheme.len();
- graphemes += 1;
+ grapheme_len += 1;
iter.next();
}
}
let token = &self.input[..offset];
self.input = &self.input[offset..];
- if is_whitespace {
- Some(WordBreakToken {
- token: " ",
- grapheme_len: 1,
- is_whitespace: true,
+ if token == "\n" {
+ Some(WordBreakToken::Newline)
+ } else if is_whitespace {
+ Some(WordBreakToken::InlineWhitespace {
+ token,
+ grapheme_len,
})
} else {
- Some(WordBreakToken {
+ Some(WordBreakToken::Word {
token,
- grapheme_len: graphemes,
- is_whitespace: false,
+ grapheme_len,
})
}
} else {
@@ -17352,66 +17368,75 @@ impl<'a> Iterator for WordBreakingTokenizer<'a> {
#[test]
fn test_word_breaking_tokenizer() {
- let tests: &[(&str, &[(&str, usize, bool)])] = &[
+ let tests: &[(&str, &[WordBreakToken<'static>])] = &[
("", &[]),
- (" ", &[(" ", 1, true)]),
- ("Ʒ", &[("Ʒ", 1, false)]),
- ("Ǽ", &[("Ǽ", 1, false)]),
- ("⋑", &[("⋑", 1, false)]),
- ("⋑⋑", &[("⋑⋑", 2, false)]),
+ (" ", &[whitespace(" ", 2)]),
+ ("Ʒ", &[word("Ʒ", 1)]),
+ ("Ǽ", &[word("Ǽ", 1)]),
+ ("⋑", &[word("⋑", 1)]),
+ ("⋑⋑", &[word("⋑⋑", 2)]),
(
"原理,进而",
- &[
- ("原", 1, false),
- ("理,", 2, false),
- ("进", 1, false),
- ("而", 1, false),
- ],
+ &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
),
(
"hello world",
- &[("hello", 5, false), (" ", 1, true), ("world", 5, false)],
+ &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
),
(
"hello, world",
- &[("hello,", 6, false), (" ", 1, true), ("world", 5, false)],
+ &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
),
(
" hello world",
&[
- (" ", 1, true),
- ("hello", 5, false),
- (" ", 1, true),
- ("world", 5, false),
+ whitespace(" ", 2),
+ word("hello", 5),
+ whitespace(" ", 1),
+ word("world", 5),
],
),
(
"这是什么 \n 钢笔",
&[
- ("这", 1, false),
- ("是", 1, false),
- ("什", 1, false),
- ("么", 1, false),
- (" ", 1, true),
- ("钢", 1, false),
- ("笔", 1, false),
+ word("这", 1),
+ word("是", 1),
+ word("什", 1),
+ word("么", 1),
+ whitespace(" ", 1),
+ newline(),
+ whitespace(" ", 1),
+ word("钢", 1),
+ word("笔", 1),
],
),
- (" mutton", &[(" ", 1, true), ("mutton", 6, false)]),
+ (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
];
+ fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
+ WordBreakToken::Word {
+ token,
+ grapheme_len,
+ }
+ }
+
+ fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
+ WordBreakToken::InlineWhitespace {
+ token,
+ grapheme_len,
+ }
+ }
+
+ fn newline() -> WordBreakToken<'static> {
+ WordBreakToken::Newline
+ }
+
for (input, result) in tests {
assert_eq!(
- WordBreakingTokenizer::new(input).collect::<Vec<_>>(),
- result
- .iter()
- .copied()
- .map(|(token, grapheme_len, is_whitespace)| WordBreakToken {
- token,
- grapheme_len,
- is_whitespace,
- })
+ WordBreakingTokenizer::new(input)
.collect::<Vec<_>>()
+ .as_slice(),
+ *result,
);
}
}
@@ -17421,6 +17446,7 @@ fn wrap_with_prefix(
unwrapped_text: String,
wrap_column: usize,
tab_size: NonZeroU32,
+ preserve_existing_whitespace: bool,
) -> String {
let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
let mut wrapped_text = String::new();
@@ -17428,27 +17454,68 @@ fn wrap_with_prefix(
let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
let mut current_line_len = line_prefix_len;
- for WordBreakToken {
- token,
- grapheme_len,
- is_whitespace,
- } in tokenizer
- {
- if current_line_len + grapheme_len > wrap_column && current_line_len != line_prefix_len {
- wrapped_text.push_str(current_line.trim_end());
- wrapped_text.push('\n');
- current_line.truncate(line_prefix.len());
- current_line_len = line_prefix_len;
- if !is_whitespace {
+ let mut in_whitespace = false;
+ for token in tokenizer {
+ let have_preceding_whitespace = in_whitespace;
+ match token {
+ WordBreakToken::Word {
+ token,
+ grapheme_len,
+ } => {
+ in_whitespace = false;
+ if current_line_len + grapheme_len > wrap_column
+ && current_line_len != line_prefix_len
+ {
+ wrapped_text.push_str(current_line.trim_end());
+ wrapped_text.push('\n');
+ current_line.truncate(line_prefix.len());
+ current_line_len = line_prefix_len;
+ }
current_line.push_str(token);
current_line_len += grapheme_len;
}
- } else if !is_whitespace {
- current_line.push_str(token);
- current_line_len += grapheme_len;
- } else if current_line_len != line_prefix_len {
- current_line.push(' ');
- current_line_len += 1;
+ WordBreakToken::InlineWhitespace {
+ mut token,
+ mut grapheme_len,
+ } => {
+ in_whitespace = true;
+ if have_preceding_whitespace && !preserve_existing_whitespace {
+ continue;
+ }
+ if !preserve_existing_whitespace {
+ token = " ";
+ grapheme_len = 1;
+ }
+ if current_line_len + grapheme_len > wrap_column {
+ wrapped_text.push_str(current_line.trim_end());
+ wrapped_text.push('\n');
+ current_line.truncate(line_prefix.len());
+ current_line_len = line_prefix_len;
+ } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
+ current_line.push_str(token);
+ current_line_len += grapheme_len;
+ }
+ }
+ WordBreakToken::Newline => {
+ in_whitespace = true;
+ if preserve_existing_whitespace {
+ wrapped_text.push_str(current_line.trim_end());
+ wrapped_text.push('\n');
+ current_line.truncate(line_prefix.len());
+ current_line_len = line_prefix_len;
+ } else if have_preceding_whitespace {
+ continue;
+ } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
+ {
+ wrapped_text.push_str(current_line.trim_end());
+ wrapped_text.push('\n');
+ current_line.truncate(line_prefix.len());
+ current_line_len = line_prefix_len;
+ } else if current_line_len != line_prefix_len {
+ current_line.push(' ');
+ current_line_len += 1;
+ }
+ }
}
}
@@ -17465,7 +17532,8 @@ fn test_wrap_with_prefix() {
"# ".to_string(),
"abcdefg".to_string(),
4,
- NonZeroU32::new(4).unwrap()
+ NonZeroU32::new(4).unwrap(),
+ false,
),
"# abcdefg"
);
@@ -17474,7 +17542,8 @@ fn test_wrap_with_prefix() {
"".to_string(),
"\thello world".to_string(),
8,
- NonZeroU32::new(4).unwrap()
+ NonZeroU32::new(4).unwrap(),
+ false,
),
"hello\nworld"
);
@@ -17483,7 +17552,8 @@ fn test_wrap_with_prefix() {
"// ".to_string(),
"xx \nyy zz aa bb cc".to_string(),
12,
- NonZeroU32::new(4).unwrap()
+ NonZeroU32::new(4).unwrap(),
+ false,
),
"// xx yy zz\n// aa bb cc"
);
@@ -17492,7 +17562,8 @@ fn test_wrap_with_prefix() {
String::new(),
"这是什么 \n 钢笔".to_string(),
3,
- NonZeroU32::new(4).unwrap()
+ NonZeroU32::new(4).unwrap(),
+ false,
),
"这是什\n么 钢\n笔"
);
@@ -1,6 +1,6 @@
use crate::{motion::Motion, object::Object, state::Mode, Vim};
use collections::HashMap;
-use editor::{display_map::ToDisplayPoint, scroll::Autoscroll, Bias, Editor};
+use editor::{display_map::ToDisplayPoint, scroll::Autoscroll, Bias, Editor, RewrapOptions};
use gpui::{actions, Context, Window};
use language::SelectionGoal;
@@ -14,7 +14,13 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
vim.update_editor(window, cx, |vim, editor, window, cx| {
editor.transact(window, cx, |editor, window, cx| {
let mut positions = vim.save_selection_starts(editor, cx);
- editor.rewrap_impl(true, cx);
+ editor.rewrap_impl(
+ RewrapOptions {
+ override_language_settings: true,
+ ..Default::default()
+ },
+ cx,
+ );
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
s.move_with(|map, selection| {
if let Some(anchor) = positions.remove(&selection.id) {
@@ -52,7 +58,13 @@ impl Vim {
motion.expand_selection(map, selection, times, false, &text_layout_details);
});
});
- editor.rewrap_impl(true, cx);
+ editor.rewrap_impl(
+ RewrapOptions {
+ override_language_settings: true,
+ ..Default::default()
+ },
+ cx,
+ );
editor.change_selections(None, window, cx, |s| {
s.move_with(|map, selection| {
let anchor = selection_starts.remove(&selection.id).unwrap();
@@ -83,7 +95,13 @@ impl Vim {
object.expand_selection(map, selection, around);
});
});
- editor.rewrap_impl(true, cx);
+ editor.rewrap_impl(
+ RewrapOptions {
+ override_language_settings: true,
+ ..Default::default()
+ },
+ cx,
+ );
editor.change_selections(None, window, cx, |s| {
s.move_with(|map, selection| {
let anchor = original_positions.remove(&selection.id).unwrap();