From e2ae0125bdf6379bbcd9b11f5713b070348ece74 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 30 Apr 2024 21:39:59 -0600 Subject: [PATCH] Fix g_ to go to last non-whitespace character Use the new helpers for yss to do less buffer parsing. --- crates/vim/src/motion.rs | 36 +++++++++- crates/vim/src/surrounds.rs | 69 +++++++++---------- crates/vim/src/vim.rs | 2 +- .../test_data/test_end_of_line_downward.json | 8 +++ 4 files changed, 77 insertions(+), 38 deletions(-) create mode 100644 crates/vim/test_data/test_end_of_line_downward.json diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 12b9b70a42..e965aa8608 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -447,6 +447,8 @@ pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) { vim.clear_operator(cx); if let Some(operator) = waiting_operator { vim.push_operator(operator, cx); + dbg!(count); + vim.update_state(|state| state.pre_count = count) } }); } @@ -755,7 +757,7 @@ impl Motion { }, NextLineStart => (next_line_start(map, point, times), SelectionGoal::None), StartOfLineDownward => (next_line_start(map, point, times - 1), SelectionGoal::None), - EndOfLineDownward => (next_line_end(map, point, times), SelectionGoal::None), + EndOfLineDownward => (last_non_whitespace(map, point, times), SelectionGoal::None), GoToColumn => (go_to_column(map, point, times), SelectionGoal::None), WindowTop => window_top(map, point, &text_layout_details, times - 1), WindowMiddle => window_middle(map, point, &text_layout_details), @@ -1422,6 +1424,28 @@ pub(crate) fn first_non_whitespace( start_offset.to_display_point(map) } +pub(crate) fn last_non_whitespace( + map: &DisplaySnapshot, + from: DisplayPoint, + count: usize, +) -> DisplayPoint { + let mut end_of_line = end_of_line(map, false, from, count).to_offset(map, Bias::Left); + let scope = map.buffer_snapshot.language_scope_at(from.to_point(map)); + dbg!(end_of_line); + for (ch, offset) in map.reverse_buffer_chars_at(end_of_line) { + if ch == '\n' { + break; + } + end_of_line = offset; + dbg!(ch, offset); + if char_kind(&scope, ch) != CharKind::Whitespace || ch == '\n' { + break; + } + } + + end_of_line.to_display_point(map) +} + pub(crate) fn start_of_line( map: &DisplaySnapshot, display_lines: bool, @@ -1899,6 +1923,16 @@ mod test { cx.assert_shared_state("one\n ˇtwo\nthree").await; } + #[gpui::test] + async fn test_end_of_line_downward(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + cx.set_shared_state("ˇ one \n two \nthree").await; + cx.simulate_shared_keystrokes(["g", "_"]).await; + cx.assert_shared_state(" onˇe \n two \nthree").await; + cx.simulate_shared_keystrokes(["2", "g", "_"]).await; + cx.assert_shared_state(" one \n twˇo \nthree").await; + } + #[gpui::test] async fn test_window_top(cx: &mut gpui::TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; diff --git a/crates/vim/src/surrounds.rs b/crates/vim/src/surrounds.rs index 67fd112141..6bc8787efc 100644 --- a/crates/vim/src/surrounds.rs +++ b/crates/vim/src/surrounds.rs @@ -1,7 +1,12 @@ -use crate::{motion::Motion, object::Object, state::Mode, Vim}; +use crate::{ + motion::{self, Motion}, + object::Object, + state::Mode, + Vim, +}; use editor::{movement, scroll::Autoscroll, Bias}; use gpui::WindowContext; -use language::{char_kind, BracketPair, CharKind}; +use language::BracketPair; use serde::Deserialize; use std::sync::Arc; #[derive(Clone, Debug, PartialEq, Eq)] @@ -23,6 +28,7 @@ impl<'de> Deserialize<'de> for SurroundsType { pub fn add_surrounds(text: Arc, target: SurroundsType, cx: &mut WindowContext) { Vim::update(cx, |vim, cx| { vim.stop_recording(); + let count = vim.take_count(cx); vim.update_active_editor(cx, |_, editor, cx| { let text_layout_details = editor.text_layout_details(cx); editor.transact(cx, |editor, cx| { @@ -52,43 +58,26 @@ pub fn add_surrounds(text: Arc, target: SurroundsType, cx: &mut WindowConte .range( &display_map, selection.clone(), - Some(1), + count, true, &text_layout_details, ) .map(|mut range| { - // The Motion::CurrentLine operation will contain the newline of the current line, - // so we need to deal with this edge case + // The Motion::CurrentLine operation will contain the newline of the current line and leading/trailing whitespace if let Motion::CurrentLine = motion { - let start_offset = - range.start.to_offset(&display_map, Bias::Left); - let scope = display_map - .buffer_snapshot - .language_scope_at(range.start.to_point(&display_map)); - for (ch, _) in display_map.buffer_chars_at(start_offset) { - if char_kind(&scope, ch) != CharKind::Whitespace { - break; - } - range.start = - movement::right(&display_map, range.start); - } - - let end_offset = - range.end.to_offset(&display_map, Bias::Left); - let scope = display_map - .buffer_snapshot - .language_scope_at(range.end.to_point(&display_map)); - for (last_ch, _) in - display_map.reverse_buffer_chars_at(end_offset) - { - if last_ch != '\n' - && char_kind(&scope, last_ch) - != CharKind::Whitespace - { - break; - } - range.end = movement::left(&display_map, range.end); - } + range.start = motion::first_non_whitespace( + &display_map, + false, + range.start, + ); + range.end = movement::saturating_right( + &display_map, + motion::last_non_whitespace( + &display_map, + movement::left(&display_map, range.end), + 1, + ), + ); } range }); @@ -651,7 +640,7 @@ mod test { cx.set_state( indoc! {" - The quˇick brown + The quˇick brown• fox jumps over the lazy dog."}, Mode::Normal, @@ -659,11 +648,19 @@ mod test { cx.simulate_keystrokes(["y", "s", "s", "{"]); cx.assert_state( indoc! {" - ˇ{ The quick brown } + ˇ{ The quick brown }• fox jumps over the lazy dog."}, Mode::Normal, ); + cx.simulate_keystrokes(["2", "y", "s", "s", ")"]); + cx.assert_state( + indoc! {" + ˇ({ The quick brown }• + fox jumps over) + the lazy dog."}, + Mode::Normal, + ); } #[gpui::test] diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 565e34f9a2..4d0837247d 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -526,7 +526,7 @@ impl Vim { | Operator::ChangeSurrounds { .. } | Operator::DeleteSurrounds ) { - self.clear_operator(cx); + self.update_state(|state| state.operator_stack.clear()); }; self.update_state(|state| state.operator_stack.push(operator)); self.sync_vim_settings(cx); diff --git a/crates/vim/test_data/test_end_of_line_downward.json b/crates/vim/test_data/test_end_of_line_downward.json new file mode 100644 index 0000000000..be73805cdc --- /dev/null +++ b/crates/vim/test_data/test_end_of_line_downward.json @@ -0,0 +1,8 @@ +{"Put":{"state":"ˇ one \n two \nthree"}} +{"Key":"g"} +{"Key":"_"} +{"Get":{"state":" onˇe \n two \nthree","mode":"Normal"}} +{"Key":"2"} +{"Key":"g"} +{"Key":"_"} +{"Get":{"state":" one \n twˇo \nthree","mode":"Normal"}}