From 5c2f27a50158c64fadac890a0f7ed6a7d6d8e6f6 Mon Sep 17 00:00:00 2001 From: Hans Date: Fri, 26 Apr 2024 11:09:06 +0800 Subject: [PATCH] Fix VIM cw on last character of a word doesn't work as expected: (#10963) At the moment, using the default expand_selection seems to do the job well, without the need for some additional logic, which may also make the code a little clearer, Fix #10945 Release Notes: - N/A --- crates/vim/src/normal/change.rs | 120 ++++++++++++------------ crates/vim/test_data/test_change_w.json | 4 + 2 files changed, 63 insertions(+), 61 deletions(-) diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index 97608d4123..4fca6b2ccd 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -31,48 +31,42 @@ pub fn change_motion(vim: &mut Vim, motion: Motion, times: Option, cx: &m editor.set_clip_at_line_ends(false, cx); editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.move_with(|map, selection| { - motion_succeeded |= if let Motion::NextWordStart { ignore_punctuation } = motion - { - expand_changed_word_selection( - map, - selection, - times, - ignore_punctuation, - &text_layout_details, - false, - ) - } else if let Motion::NextSubwordStart { ignore_punctuation } = motion { - expand_changed_word_selection( - map, - selection, - times, - ignore_punctuation, - &text_layout_details, - true, - ) - } else { - let result = motion.expand_selection( - map, - selection, - times, - false, - &text_layout_details, - ); - if let Motion::CurrentLine = motion { - let mut start_offset = selection.start.to_offset(map, Bias::Left); - let scope = map - .buffer_snapshot - .language_scope_at(selection.start.to_point(&map)); - for (ch, offset) in map.buffer_chars_at(start_offset) { - if ch == '\n' || char_kind(&scope, ch) != CharKind::Whitespace { - break; - } - start_offset = offset + ch.len_utf8(); - } - selection.start = start_offset.to_display_point(map); + motion_succeeded |= match motion { + Motion::NextWordStart { ignore_punctuation } + | Motion::NextSubwordStart { ignore_punctuation } => { + expand_changed_word_selection( + map, + selection, + times, + ignore_punctuation, + &text_layout_details, + motion == Motion::NextSubwordStart { ignore_punctuation }, + ) } - result - }; + _ => { + let result = motion.expand_selection( + map, + selection, + times, + false, + &text_layout_details, + ); + if let Motion::CurrentLine = motion { + let mut start_offset = selection.start.to_offset(map, Bias::Left); + let scope = map + .buffer_snapshot + .language_scope_at(selection.start.to_point(&map)); + for (ch, offset) in map.buffer_chars_at(start_offset) { + if ch == '\n' || char_kind(&scope, ch) != CharKind::Whitespace { + break; + } + start_offset = offset + ch.len_utf8(); + } + selection.start = start_offset.to_display_point(map); + } + result + } + } }); }); copy_selections_content(vim, editor, motion.linewise(), cx); @@ -116,8 +110,8 @@ pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut Windo // Special case: "cw" and "cW" are treated like "ce" and "cE" if the cursor is // on a non-blank. This is because "cw" is interpreted as change-word, and a // word does not include the following white space. {Vi: "cw" when on a blank -// followed by other blanks changes only the first blank; this is probably a -// bug, because "dw" deletes all the blanks} +// followed by other blanks changes only the first blank; this is probably a +// bug, because "dw" deletes all the blanks} fn expand_changed_word_selection( map: &DisplaySnapshot, selection: &mut Selection, @@ -126,7 +120,7 @@ fn expand_changed_word_selection( text_layout_details: &TextLayoutDetails, use_subword: bool, ) -> bool { - if times.is_none() || times.unwrap() == 1 { + let is_in_word = || { let scope = map .buffer_snapshot .language_scope_at(selection.start.to_point(map)); @@ -135,25 +129,28 @@ fn expand_changed_word_selection( .next() .map(|(c, _)| char_kind(&scope, c) != CharKind::Whitespace) .unwrap_or_default(); - - if in_word { - if use_subword { - selection.end = - motion::next_subword_end(map, selection.end, ignore_punctuation, 1, false); - } else { - selection.end = - motion::next_word_end(map, selection.end, ignore_punctuation, 1, false); + return in_word; + }; + if (times.is_none() || times.unwrap() == 1) && is_in_word() { + let next_char = map + .buffer_chars_at( + motion::next_char(map, selection.end, false).to_offset(map, Bias::Left), + ) + .next(); + match next_char { + Some((' ', _)) => selection.end = motion::next_char(map, selection.end, false), + _ => { + if use_subword { + selection.end = + motion::next_subword_end(map, selection.end, ignore_punctuation, 1, false); + } else { + selection.end = + motion::next_word_end(map, selection.end, ignore_punctuation, 1, false); + } + selection.end = motion::next_char(map, selection.end, false); } - selection.end = motion::next_char(map, selection.end, false); - true - } else { - let motion = if use_subword { - Motion::NextSubwordStart { ignore_punctuation } - } else { - Motion::NextWordStart { ignore_punctuation } - }; - motion.expand_selection(map, selection, None, false, &text_layout_details) } + true } else { let motion = if use_subword { Motion::NextSubwordStart { ignore_punctuation } @@ -209,6 +206,7 @@ mod test { cx.assert("Teˇst").await; cx.assert("Tˇest test").await; cx.assert("Testˇ test").await; + cx.assert("Tesˇt test").await; cx.assert(indoc! {" Test teˇst test"}) diff --git a/crates/vim/test_data/test_change_w.json b/crates/vim/test_data/test_change_w.json index 586fbdf799..27be543532 100644 --- a/crates/vim/test_data/test_change_w.json +++ b/crates/vim/test_data/test_change_w.json @@ -10,6 +10,10 @@ {"Key":"c"} {"Key":"w"} {"Get":{"state":"Testˇtest","mode":"Insert"}} +{"Put":{"state":"Tesˇt test"}} +{"Key":"c"} +{"Key":"w"} +{"Get":{"state":"Tesˇ test","mode":"Insert"}} {"Put":{"state":"Test teˇst\ntest"}} {"Key":"c"} {"Key":"w"}