From 8566b64668a871bd44c66f06e62b47151033e11d Mon Sep 17 00:00:00 2001 From: Zachiah Sawyer Date: Tue, 7 May 2024 11:49:15 -0600 Subject: [PATCH 1/4] vim: Add basic mark support --- assets/keymaps/vim.json | 3 + crates/vim/src/motion.rs | 12 +++- crates/vim/src/normal.rs | 1 + crates/vim/src/normal/mark.rs | 86 ++++++++++++++++++++++++++++ crates/vim/src/state.rs | 14 ++++- crates/vim/src/test.rs | 17 ++++++ crates/vim/src/vim.rs | 8 ++- crates/vim/test_data/test_marks.json | 15 +++++ 8 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 crates/vim/src/normal/mark.rs create mode 100644 crates/vim/test_data/test_marks.json diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 1bb362a32407..552624c82f18 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -117,6 +117,9 @@ } } ], + "m": ["vim::PushOperator", "Mark"], + "'": ["vim::PushOperator", { "Jump": { "line": true } }], + "`": ["vim::PushOperator", { "Jump": { "line": false } }], ";": "vim::RepeatFind", ",": "vim::RepeatFindReversed", "ctrl-o": "pane::GoBack", diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 2c50f52f5a35..ee34ede20319 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -13,7 +13,7 @@ use std::ops::Range; use workspace::Workspace; use crate::{ - normal::normal_motion, + normal::{mark, normal_motion}, state::{Mode, Operator}, surrounds::SurroundsType, utils::coerce_punctuation, @@ -105,6 +105,10 @@ pub enum Motion { prior_selections: Vec>, new_selections: Vec>, }, + Jump { + anchor: Anchor, + line: bool, + }, } #[derive(Clone, Deserialize, PartialEq)] @@ -492,6 +496,7 @@ impl Motion { | FindBackward { .. } | RepeatFind { .. } | RepeatFindReversed { .. } + | Jump { .. } | ZedSearchResult { .. } => false, } } @@ -531,7 +536,8 @@ impl Motion { | WindowMiddle | WindowBottom | NextLineStart - | ZedSearchResult { .. } => false, + | ZedSearchResult { .. } + | Jump { .. } => false, } } @@ -570,6 +576,7 @@ impl Motion { | PreviousSubwordStart { .. } | FirstNonWhitespace { .. } | FindBackward { .. } + | Jump { .. } | ZedSearchResult { .. } => false, RepeatFind { last_find: motion } | RepeatFindReversed { last_find: motion } => { motion.inclusive() @@ -761,6 +768,7 @@ impl Motion { WindowTop => window_top(map, point, &text_layout_details, times - 1), WindowMiddle => window_middle(map, point, &text_layout_details), WindowBottom => window_bottom(map, point, &text_layout_details, times - 1), + Jump { line, anchor } => mark::jump_motion(map, *anchor, *line), ZedSearchResult { new_selections, .. } => { // There will be only one selection, as // Search::SelectNextMatch selects a single match. diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index e0eb1e46d112..9510e4f05078 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -2,6 +2,7 @@ mod case; mod change; mod delete; mod increment; +pub(crate) mod mark; mod paste; pub(crate) mod repeat; mod scroll; diff --git a/crates/vim/src/normal/mark.rs b/crates/vim/src/normal/mark.rs new file mode 100644 index 000000000000..8359b7ca8d29 --- /dev/null +++ b/crates/vim/src/normal/mark.rs @@ -0,0 +1,86 @@ +use std::{ops::Range, sync::Arc}; + +use collections::HashSet; +use editor::{ + display_map::{DisplaySnapshot, ToDisplayPoint}, + Anchor, DisplayPoint, +}; +use gpui::WindowContext; +use language::SelectionGoal; + +use crate::{ + motion::{self, Motion}, + Vim, +}; + +pub fn create_mark(vim: &mut Vim, text: Arc, cx: &mut WindowContext) { + let Some(anchors) = vim.update_active_editor(cx, |_, editor, _| { + editor + .selections + .disjoint_anchors() + .iter() + .map(|s| s.head().clone()) + .collect::>() + }) else { + return; + }; + + vim.update_state(|state| state.marks.insert(text.to_string(), anchors)); + vim.clear_operator(cx); +} + +pub fn jump(text: Arc, line: bool, cx: &mut WindowContext) { + let Some(anchors) = Vim::read(cx).state().marks.get(&*text).cloned() else { + return; + }; + + Vim::update(cx, |vim, cx| { + vim.pop_operator(cx); + }); + + let is_active_operator = Vim::read(cx).state().active_operator().is_some(); + if is_active_operator { + if let Some(anchor) = anchors.last() { + motion::motion( + Motion::Jump { + anchor: *anchor, + line, + }, + cx, + ) + } + return; + } else { + Vim::update(cx, |vim, cx| { + vim.update_active_editor(cx, |_, editor, cx| { + let map = editor.snapshot(cx); + let mut ranges: HashSet> = HashSet::default(); + for mut anchor in anchors { + if line { + let mut point = anchor.to_display_point(&map.display_snapshot); + point = motion::first_non_whitespace(&map.display_snapshot, false, point); + anchor = map + .display_snapshot + .buffer_snapshot + .anchor_before(point.to_point(&map.display_snapshot)); + } + ranges.insert(anchor..anchor); + } + editor.change_selections(None, cx, |s| s.select_anchor_ranges(ranges)) + }); + }) + } +} + +pub fn jump_motion( + map: &DisplaySnapshot, + anchor: Anchor, + line: bool, +) -> (DisplayPoint, SelectionGoal) { + let mut point = anchor.to_display_point(map); + if line { + point = motion::first_non_whitespace(map, false, point) + } + + (point, SelectionGoal::None) +} diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 9ece818b16ca..ed9861a97ae4 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -59,6 +59,8 @@ pub enum Operator { AddSurrounds { target: Option }, ChangeSurrounds { target: Option }, DeleteSurrounds, + Mark, + Jump { line: bool }, } #[derive(Default, Clone)] @@ -74,6 +76,8 @@ pub struct EditorState { pub operator_stack: Vec, pub replacements: Vec<(Range, String)>, + pub marks: HashMap>, + pub current_tx: Option, pub current_anchor: Option>, pub undo_modes: HashMap, @@ -172,7 +176,10 @@ impl EditorState { } matches!( self.operator_stack.last(), - Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) + Some(Operator::FindForward { .. }) + | Some(Operator::FindBackward { .. }) + | Some(Operator::Mark) + | Some(Operator::Jump { .. }) ) } @@ -254,6 +261,9 @@ impl Operator { Operator::AddSurrounds { .. } => "ys", Operator::ChangeSurrounds { .. } => "cs", Operator::DeleteSurrounds => "ds", + Operator::Mark => "m", + Operator::Jump { line: true } => "'", + Operator::Jump { line: false } => "`", } } @@ -261,6 +271,8 @@ impl Operator { match self { Operator::Object { .. } | Operator::ChangeSurrounds { target: None } => &["VimObject"], Operator::FindForward { .. } + | Operator::Mark + | Operator::Jump { .. } | Operator::FindBackward { .. } | Operator::Replace | Operator::AddSurrounds { target: Some(_) } diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 29aefbae76f0..10bd876b7ba4 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -1073,3 +1073,20 @@ async fn test_mouse_selection(cx: &mut TestAppContext) { cx.assert_state("one «ˇtwo» three", Mode::Visual) } + +#[gpui::test] +async fn test_marks(cx: &mut TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state("line one\nline ˇtwo\nline three").await; + cx.simulate_shared_keystrokes(["m", "a", "l", "'", "a"]) + .await; + cx.assert_shared_state("line one\nˇline two\nline three") + .await; + cx.simulate_shared_keystrokes(["`", "a"]).await; + cx.assert_shared_state("line one\nline ˇtwo\nline three") + .await; + + cx.simulate_shared_keystrokes(["^", "d", "`", "a"]).await; + cx.assert_shared_state("line one\nˇtwo\nline three").await; +} diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 95645f83de57..5aebe43afcd5 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -194,7 +194,9 @@ fn observe_keystrokes(keystroke_event: &KeystrokeEvent, cx: &mut WindowContext) | Operator::Replace | Operator::AddSurrounds { .. } | Operator::ChangeSurrounds { .. } - | Operator::DeleteSurrounds, + | Operator::DeleteSurrounds + | Operator::Mark + | Operator::Jump { .. }, ) => {} Some(_) => { vim.clear_operator(cx); @@ -706,6 +708,10 @@ impl Vim { } _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)), }, + Some(Operator::Mark) => { + Vim::update(cx, |vim, cx| normal::mark::create_mark(vim, text, cx)) + } + Some(Operator::Jump { line }) => normal::mark::jump(text, line, cx), _ => match Vim::read(cx).state().mode { Mode::Replace => multi_replace(text, cx), _ => {} diff --git a/crates/vim/test_data/test_marks.json b/crates/vim/test_data/test_marks.json new file mode 100644 index 000000000000..ff02c58728a3 --- /dev/null +++ b/crates/vim/test_data/test_marks.json @@ -0,0 +1,15 @@ +{"Put":{"state":"line one\nline ˇtwo\nline three"}} +{"Key":"m"} +{"Key":"a"} +{"Key":"l"} +{"Key":"'"} +{"Key":"a"} +{"Get":{"state":"line one\nˇline two\nline three","mode":"Normal"}} +{"Key":"`"} +{"Key":"a"} +{"Get":{"state":"line one\nline ˇtwo\nline three","mode":"Normal"}} +{"Key":"^"} +{"Key":"d"} +{"Key":"`"} +{"Key":"a"} +{"Get":{"state":"line one\nˇtwo\nline three","mode":"Normal"}} From 0dba953ee8b98f15557969f0c8b0d73266a54e40 Mon Sep 17 00:00:00 2001 From: Zachiah Sawyer Date: Wed, 8 May 2024 12:00:24 -0600 Subject: [PATCH 2/4] vim: Add support for <, >, and ^, marks --- crates/vim/src/motion.rs | 3 +- crates/vim/src/normal/mark.rs | 67 +++++++++- crates/vim/src/test.rs | 115 +++++++++++++++++- crates/vim/src/utils.rs | 18 +++ crates/vim/src/vim.rs | 25 +++- crates/vim/test_data/test_builtin_marks.json | 36 ++++++ crates/vim/test_data/test_caret_mark.json | 26 ++++ .../vim/test_data/test_lowercase_marks.json | 15 +++ crates/vim/test_data/test_lt_gt_marks.json | 18 +++ crates/vim/test_data/test_period_mark.json | 14 +++ 10 files changed, 325 insertions(+), 12 deletions(-) create mode 100644 crates/vim/test_data/test_builtin_marks.json create mode 100644 crates/vim/test_data/test_caret_mark.json create mode 100644 crates/vim/test_data/test_lowercase_marks.json create mode 100644 crates/vim/test_data/test_lt_gt_marks.json create mode 100644 crates/vim/test_data/test_period_mark.json diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index ee34ede20319..50f3e3f23118 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -473,6 +473,7 @@ impl Motion { | WindowTop | WindowMiddle | WindowBottom + | Jump { line: true, .. } | EndOfParagraph => true, EndOfLine { .. } | Matching @@ -496,7 +497,7 @@ impl Motion { | FindBackward { .. } | RepeatFind { .. } | RepeatFindReversed { .. } - | Jump { .. } + | Jump { line: false, .. } | ZedSearchResult { .. } => false, } } diff --git a/crates/vim/src/normal/mark.rs b/crates/vim/src/normal/mark.rs index 8359b7ca8d29..8824a1ad77d4 100644 --- a/crates/vim/src/normal/mark.rs +++ b/crates/vim/src/normal/mark.rs @@ -3,7 +3,7 @@ use std::{ops::Range, sync::Arc}; use collections::HashSet; use editor::{ display_map::{DisplaySnapshot, ToDisplayPoint}, - Anchor, DisplayPoint, + movement, Anchor, Bias, DisplayPoint, }; use gpui::WindowContext; use language::SelectionGoal; @@ -13,13 +13,13 @@ use crate::{ Vim, }; -pub fn create_mark(vim: &mut Vim, text: Arc, cx: &mut WindowContext) { +pub fn create_mark(vim: &mut Vim, text: Arc, tail: bool, cx: &mut WindowContext) { let Some(anchors) = vim.update_active_editor(cx, |_, editor, _| { editor .selections .disjoint_anchors() .iter() - .map(|s| s.head().clone()) + .map(|s| if tail { s.tail() } else { s.head() }) .collect::>() }) else { return; @@ -29,15 +29,72 @@ pub fn create_mark(vim: &mut Vim, text: Arc, cx: &mut WindowContext) { vim.clear_operator(cx); } -pub fn jump(text: Arc, line: bool, cx: &mut WindowContext) { - let Some(anchors) = Vim::read(cx).state().marks.get(&*text).cloned() else { +pub fn create_mark_after(vim: &mut Vim, text: Arc, cx: &mut WindowContext) { + let Some(anchors) = vim.update_active_editor(cx, |_, editor, cx| { + let (map, selections) = editor.selections.all_display(cx); + selections + .into_iter() + .map(|selection| { + let point = movement::saturating_right(&map, selection.tail()); + map.buffer_snapshot + .anchor_before(point.to_offset(&map, Bias::Left)) + }) + .collect::>() + }) else { return; }; + vim.update_state(|state| state.marks.insert(text.to_string(), anchors)); + vim.clear_operator(cx); +} + +pub fn create_mark_before(vim: &mut Vim, text: Arc, cx: &mut WindowContext) { + let Some(anchors) = vim.update_active_editor(cx, |_, editor, cx| { + let (map, selections) = editor.selections.all_display(cx); + selections + .into_iter() + .map(|selection| { + let point = movement::saturating_left(&map, selection.head()); + map.buffer_snapshot + .anchor_before(point.to_offset(&map, Bias::Left)) + }) + .collect::>() + }) else { + return; + }; + + vim.update_state(|state| state.marks.insert(text.to_string(), anchors)); + vim.clear_operator(cx); +} + +pub fn jump(text: Arc, line: bool, cx: &mut WindowContext) { + let anchors = match &*text { + "{" | "}" => Vim::update(cx, |vim, cx| { + vim.update_active_editor(cx, |_, editor, cx| { + let (map, selections) = editor.selections.all_display(cx); + selections + .into_iter() + .map(|selection| { + let point = if &*text == "{" { + movement::start_of_paragraph(&map, selection.head(), 1) + } else { + movement::end_of_paragraph(&map, selection.head(), 1) + }; + map.buffer_snapshot + .anchor_before(point.to_offset(&map, Bias::Left)) + }) + .collect::>() + }) + }), + _ => Vim::read(cx).state().marks.get(&*text).cloned(), + }; + Vim::update(cx, |vim, cx| { vim.pop_operator(cx); }); + let Some(anchors) = anchors else { return }; + let is_active_operator = Vim::read(cx).state().active_operator().is_some(); if is_active_operator { if let Some(anchor) = anchors.last() { diff --git a/crates/vim/src/test.rs b/crates/vim/src/test.rs index 10bd876b7ba4..2d3946e9fbb4 100644 --- a/crates/vim/src/test.rs +++ b/crates/vim/src/test.rs @@ -1075,7 +1075,7 @@ async fn test_mouse_selection(cx: &mut TestAppContext) { } #[gpui::test] -async fn test_marks(cx: &mut TestAppContext) { +async fn test_lowercase_marks(cx: &mut TestAppContext) { let mut cx = NeovimBackedTestContext::new(cx).await; cx.set_shared_state("line one\nline ˇtwo\nline three").await; @@ -1090,3 +1090,116 @@ async fn test_marks(cx: &mut TestAppContext) { cx.simulate_shared_keystrokes(["^", "d", "`", "a"]).await; cx.assert_shared_state("line one\nˇtwo\nline three").await; } + +#[gpui::test] +async fn test_lt_gt_marks(cx: &mut TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(indoc!( + " + Line one + Line two + Line ˇthree + Line four + Line five + " + )) + .await; + + cx.simulate_shared_keystrokes(["v", "j", "escape", "k", "k"]) + .await; + + cx.simulate_shared_keystrokes(["'", "<"]).await; + cx.assert_shared_state(indoc!( + " + Line one + Line two + ˇLine three + Line four + Line five + " + )) + .await; + + cx.simulate_shared_keystrokes(["`", "<"]).await; + cx.assert_shared_state(indoc!( + " + Line one + Line two + Line ˇthree + Line four + Line five + " + )) + .await; + + cx.simulate_shared_keystrokes(["'", ">"]).await; + cx.assert_shared_state(indoc!( + " + Line one + Line two + Line three + ˇLine four + Line five + " + )) + .await; + + cx.simulate_shared_keystrokes(["`", ">"]).await; + cx.assert_shared_state(indoc!( + " + Line one + Line two + Line three + Line ˇfour + Line five + " + )) + .await; +} + +#[gpui::test] +async fn test_caret_mark(cx: &mut TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state(indoc!( + " + Line one + Line two + Line three + ˇLine four + Line five + " + )) + .await; + + cx.simulate_shared_keystrokes([ + "c", "w", "shift-s", "t", "r", "a", "i", "g", "h", "t", " ", "t", "h", "i", "n", "g", + "escape", "j", "j", + ]) + .await; + + cx.simulate_shared_keystrokes(["'", "^"]).await; + cx.assert_shared_state(indoc!( + " + Line one + Line two + Line three + ˇStraight thing four + Line five + " + )) + .await; + + cx.simulate_shared_keystrokes(["`", "^"]).await; + cx.assert_shared_state(indoc!( + " + Line one + Line two + Line three + Straight thingˇ four + Line five + " + )) + .await; +} diff --git a/crates/vim/src/utils.rs b/crates/vim/src/utils.rs index 3af455a3090e..1888b303eb85 100644 --- a/crates/vim/src/utils.rs +++ b/crates/vim/src/utils.rs @@ -39,6 +39,24 @@ fn copy_selections_content_internal( let mut text = String::new(); let mut clipboard_selections = Vec::with_capacity(selections.len()); let mut ranges_to_highlight = Vec::new(); + + vim.update_state(|state| { + state.marks.insert( + "[".to_string(), + selections + .iter() + .map(|s| buffer.anchor_before(s.start)) + .collect(), + ); + state.marks.insert( + "]".to_string(), + selections + .iter() + .map(|s| buffer.anchor_after(s.end)) + .collect(), + ) + }); + { let mut is_first = true; for selection in selections.iter() { diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 5aebe43afcd5..20b07c8046c0 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -30,7 +30,10 @@ use gpui::{ use language::{CursorShape, Point, SelectionGoal, TransactionId}; pub use mode_indicator::ModeIndicator; use motion::Motion; -use normal::normal_replace; +use normal::{ + mark::{create_mark, create_mark_after, create_mark_before}, + normal_replace, +}; use replace::multi_replace; use schemars::JsonSchema; use serde::Deserialize; @@ -420,6 +423,10 @@ impl Vim { // Sync editor settings like clip mode self.sync_vim_settings(cx); + if mode != Mode::Insert && last_mode == Mode::Insert { + create_mark_after(self, "^".into(), cx) + } + if leave_selections { return; } @@ -616,6 +623,7 @@ impl Vim { let is_multicursor = editor.read(cx).selections.count() > 1; let state = self.state(); + let mut is_visual = state.mode.is_visual(); if state.mode == Mode::Insert && state.current_tx.is_some() { if state.current_anchor.is_none() { self.update_state(|state| state.current_anchor = Some(newest)); @@ -632,11 +640,18 @@ impl Vim { } else { self.switch_mode(Mode::Visual, false, cx) } + is_visual = true; } else if newest.start == newest.end && !is_multicursor && [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&state.mode) { - self.switch_mode(Mode::Normal, true, cx) + self.switch_mode(Mode::Normal, true, cx); + is_visual = false; + } + + if is_visual { + create_mark_before(self, ">".into(), cx); + create_mark(self, "<".into(), true, cx) } } @@ -708,9 +723,9 @@ impl Vim { } _ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)), }, - Some(Operator::Mark) => { - Vim::update(cx, |vim, cx| normal::mark::create_mark(vim, text, cx)) - } + Some(Operator::Mark) => Vim::update(cx, |vim, cx| { + normal::mark::create_mark(vim, text, false, cx) + }), Some(Operator::Jump { line }) => normal::mark::jump(text, line, cx), _ => match Vim::read(cx).state().mode { Mode::Replace => multi_replace(text, cx), diff --git a/crates/vim/test_data/test_builtin_marks.json b/crates/vim/test_data/test_builtin_marks.json new file mode 100644 index 000000000000..0d05385960aa --- /dev/null +++ b/crates/vim/test_data/test_builtin_marks.json @@ -0,0 +1,36 @@ +{"Put":{"state":"Line one\nLine two\nLine ˇthree\nLine four\nLine five\n"}} +{"Key":"v"} +{"Key":"j"} +{"Key":"escape"} +{"Key":"k"} +{"Key":"k"} +{"Key":"'"} +{"Key":"<"} +{"Get":{"state":"Line one\nLine two\nˇLine three\nLine four\nLine five\n","mode":"Normal"}} +{"Key":"`"} +{"Key":"<"} +{"Get":{"state":"Line one\nLine two\nLine ˇthree\nLine four\nLine five\n","mode":"Normal"}} +{"Key":"'"} +{"Key":">"} +{"Get":{"state":"Line one\nLine two\nLine three\nˇLine four\nLine five\n","mode":"Normal"}} +{"Key":"`"} +{"Key":">"} +{"Get":{"state":"Line one\nLine two\nLine three\nLine ˇfour\nLine five\n","mode":"Normal"}} +{"Key":"g"} +{"Key":"g"} +{"Key":"^"} +{"Key":"j"} +{"Key":"j"} +{"Key":"l"} +{"Key":"l"} +{"Key":"c"} +{"Key":"e"} +{"Key":"k"} +{"Key":"e"} +{"Key":"escape"} +{"Key":"'"} +{"Key":"."} +{"Get":{"state":"Line one\nLine two\nˇLike three\nLine four\nLine five\n","mode":"Normal"}} +{"Key":"`"} +{"Key":"."} +{"Get":{"state":"Line one\nLine two\nLiˇke three\nLine four\nLine five\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_caret_mark.json b/crates/vim/test_data/test_caret_mark.json new file mode 100644 index 000000000000..01edb7e836db --- /dev/null +++ b/crates/vim/test_data/test_caret_mark.json @@ -0,0 +1,26 @@ +{"Put":{"state":"Line one\nLine two\nLine three\nˇLine four\nLine five\n"}} +{"Key":"c"} +{"Key":"w"} +{"Key":"shift-s"} +{"Key":"t"} +{"Key":"r"} +{"Key":"a"} +{"Key":"i"} +{"Key":"g"} +{"Key":"h"} +{"Key":"t"} +{"Key":" "} +{"Key":"t"} +{"Key":"h"} +{"Key":"i"} +{"Key":"n"} +{"Key":"g"} +{"Key":"escape"} +{"Key":"j"} +{"Key":"j"} +{"Key":"'"} +{"Key":"^"} +{"Get":{"state":"Line one\nLine two\nLine three\nˇStraight thing four\nLine five\n","mode":"Normal"}} +{"Key":"`"} +{"Key":"^"} +{"Get":{"state":"Line one\nLine two\nLine three\nStraight thingˇ four\nLine five\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_lowercase_marks.json b/crates/vim/test_data/test_lowercase_marks.json new file mode 100644 index 000000000000..ff02c58728a3 --- /dev/null +++ b/crates/vim/test_data/test_lowercase_marks.json @@ -0,0 +1,15 @@ +{"Put":{"state":"line one\nline ˇtwo\nline three"}} +{"Key":"m"} +{"Key":"a"} +{"Key":"l"} +{"Key":"'"} +{"Key":"a"} +{"Get":{"state":"line one\nˇline two\nline three","mode":"Normal"}} +{"Key":"`"} +{"Key":"a"} +{"Get":{"state":"line one\nline ˇtwo\nline three","mode":"Normal"}} +{"Key":"^"} +{"Key":"d"} +{"Key":"`"} +{"Key":"a"} +{"Get":{"state":"line one\nˇtwo\nline three","mode":"Normal"}} diff --git a/crates/vim/test_data/test_lt_gt_marks.json b/crates/vim/test_data/test_lt_gt_marks.json new file mode 100644 index 000000000000..acd750daddfd --- /dev/null +++ b/crates/vim/test_data/test_lt_gt_marks.json @@ -0,0 +1,18 @@ +{"Put":{"state":"Line one\nLine two\nLine ˇthree\nLine four\nLine five\n"}} +{"Key":"v"} +{"Key":"j"} +{"Key":"escape"} +{"Key":"k"} +{"Key":"k"} +{"Key":"'"} +{"Key":"<"} +{"Get":{"state":"Line one\nLine two\nˇLine three\nLine four\nLine five\n","mode":"Normal"}} +{"Key":"`"} +{"Key":"<"} +{"Get":{"state":"Line one\nLine two\nLine ˇthree\nLine four\nLine five\n","mode":"Normal"}} +{"Key":"'"} +{"Key":">"} +{"Get":{"state":"Line one\nLine two\nLine three\nˇLine four\nLine five\n","mode":"Normal"}} +{"Key":"`"} +{"Key":">"} +{"Get":{"state":"Line one\nLine two\nLine three\nLine ˇfour\nLine five\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_period_mark.json b/crates/vim/test_data/test_period_mark.json new file mode 100644 index 000000000000..6d3acea83c90 --- /dev/null +++ b/crates/vim/test_data/test_period_mark.json @@ -0,0 +1,14 @@ +{"Put":{"state":"Line one\nLine two\nLiˇne three\nLine four\nLine five\n"}} +{"Key":"c"} +{"Key":"e"} +{"Key":"k"} +{"Key":"e"} +{"Key":"escape"} +{"Key":"j"} +{"Key":"j"} +{"Key":"'"} +{"Key":"."} +{"Get":{"state":"Line one\nLine two\nˇLike three\nLine four\nLine five\n","mode":"Normal"}} +{"Key":"`"} +{"Key":"."} +{"Get":{"state":"Line one\nLine two\nLiˇke three\nLine four\nLine five\n","mode":"Normal"}} From ecd328d27ee47dbb02266927951f3e74b0afb4d2 Mon Sep 17 00:00:00 2001 From: Zachiah Sawyer Date: Thu, 9 May 2024 08:05:29 -0600 Subject: [PATCH 3/4] vim: Make jumping to marks scroll --- crates/vim/src/normal/mark.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/vim/src/normal/mark.rs b/crates/vim/src/normal/mark.rs index 8824a1ad77d4..ee8862b245b1 100644 --- a/crates/vim/src/normal/mark.rs +++ b/crates/vim/src/normal/mark.rs @@ -3,7 +3,9 @@ use std::{ops::Range, sync::Arc}; use collections::HashSet; use editor::{ display_map::{DisplaySnapshot, ToDisplayPoint}, - movement, Anchor, Bias, DisplayPoint, + movement, + scroll::Autoscroll, + Anchor, Bias, DisplayPoint, }; use gpui::WindowContext; use language::SelectionGoal; @@ -24,7 +26,6 @@ pub fn create_mark(vim: &mut Vim, text: Arc, tail: bool, cx: &mut WindowCon }) else { return; }; - vim.update_state(|state| state.marks.insert(text.to_string(), anchors)); vim.clear_operator(cx); } @@ -123,7 +124,9 @@ pub fn jump(text: Arc, line: bool, cx: &mut WindowContext) { } ranges.insert(anchor..anchor); } - editor.change_selections(None, cx, |s| s.select_anchor_ranges(ranges)) + editor.change_selections(Some(Autoscroll::fit()), cx, |s| { + s.select_anchor_ranges(ranges) + }) }); }) } From 22fd2cccec9469b30e9e16fd5e2d0620963c2ecc Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 9 May 2024 16:31:45 -0600 Subject: [PATCH 4/4] Don't re-order selections --- crates/vim/src/normal/mark.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/vim/src/normal/mark.rs b/crates/vim/src/normal/mark.rs index ee8862b245b1..0c6ff2f13777 100644 --- a/crates/vim/src/normal/mark.rs +++ b/crates/vim/src/normal/mark.rs @@ -1,6 +1,5 @@ use std::{ops::Range, sync::Arc}; -use collections::HashSet; use editor::{ display_map::{DisplaySnapshot, ToDisplayPoint}, movement, @@ -112,7 +111,7 @@ pub fn jump(text: Arc, line: bool, cx: &mut WindowContext) { Vim::update(cx, |vim, cx| { vim.update_active_editor(cx, |_, editor, cx| { let map = editor.snapshot(cx); - let mut ranges: HashSet> = HashSet::default(); + let mut ranges: Vec> = Vec::new(); for mut anchor in anchors { if line { let mut point = anchor.to_display_point(&map.display_snapshot); @@ -122,7 +121,9 @@ pub fn jump(text: Arc, line: bool, cx: &mut WindowContext) { .buffer_snapshot .anchor_before(point.to_point(&map.display_snapshot)); } - ranges.insert(anchor..anchor); + if ranges.last() != Some(&(anchor..anchor)) { + ranges.push(anchor..anchor); + } } editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_anchor_ranges(ranges)