Skip to content

Commit

Permalink
vim: Add basic mark support
Browse files Browse the repository at this point in the history
  • Loading branch information
Zachiah committed May 7, 2024
1 parent f2a4151 commit 8566b64
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 4 deletions.
3 changes: 3 additions & 0 deletions assets/keymaps/vim.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 10 additions & 2 deletions crates/vim/src/motion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -105,6 +105,10 @@ pub enum Motion {
prior_selections: Vec<Range<Anchor>>,
new_selections: Vec<Range<Anchor>>,
},
Jump {
anchor: Anchor,
line: bool,
},
}

#[derive(Clone, Deserialize, PartialEq)]
Expand Down Expand Up @@ -492,6 +496,7 @@ impl Motion {
| FindBackward { .. }
| RepeatFind { .. }
| RepeatFindReversed { .. }
| Jump { .. }
| ZedSearchResult { .. } => false,
}
}
Expand Down Expand Up @@ -531,7 +536,8 @@ impl Motion {
| WindowMiddle
| WindowBottom
| NextLineStart
| ZedSearchResult { .. } => false,
| ZedSearchResult { .. }
| Jump { .. } => false,
}
}

Expand Down Expand Up @@ -570,6 +576,7 @@ impl Motion {
| PreviousSubwordStart { .. }
| FirstNonWhitespace { .. }
| FindBackward { .. }
| Jump { .. }
| ZedSearchResult { .. } => false,
RepeatFind { last_find: motion } | RepeatFindReversed { last_find: motion } => {
motion.inclusive()
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions crates/vim/src/normal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod case;
mod change;
mod delete;
mod increment;
pub(crate) mod mark;
mod paste;
pub(crate) mod repeat;
mod scroll;
Expand Down
86 changes: 86 additions & 0 deletions crates/vim/src/normal/mark.rs
Original file line number Diff line number Diff line change
@@ -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<str>, cx: &mut WindowContext) {
let Some(anchors) = vim.update_active_editor(cx, |_, editor, _| {
editor
.selections
.disjoint_anchors()
.iter()
.map(|s| s.head().clone())
.collect::<Vec<_>>()
}) else {
return;
};

vim.update_state(|state| state.marks.insert(text.to_string(), anchors));
vim.clear_operator(cx);
}

pub fn jump(text: Arc<str>, 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<Range<Anchor>> = 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)
}
14 changes: 13 additions & 1 deletion crates/vim/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ pub enum Operator {
AddSurrounds { target: Option<SurroundsType> },
ChangeSurrounds { target: Option<Object> },
DeleteSurrounds,
Mark,
Jump { line: bool },
}

#[derive(Default, Clone)]
Expand All @@ -74,6 +76,8 @@ pub struct EditorState {
pub operator_stack: Vec<Operator>,
pub replacements: Vec<(Range<editor::Anchor>, String)>,

pub marks: HashMap<String, Vec<Anchor>>,

pub current_tx: Option<TransactionId>,
pub current_anchor: Option<Selection<Anchor>>,
pub undo_modes: HashMap<TransactionId, Mode>,
Expand Down Expand Up @@ -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 { .. })
)
}

Expand Down Expand Up @@ -254,13 +261,18 @@ impl Operator {
Operator::AddSurrounds { .. } => "ys",
Operator::ChangeSurrounds { .. } => "cs",
Operator::DeleteSurrounds => "ds",
Operator::Mark => "m",
Operator::Jump { line: true } => "'",
Operator::Jump { line: false } => "`",
}
}

pub fn context_flags(&self) -> &'static [&'static str] {
match self {
Operator::Object { .. } | Operator::ChangeSurrounds { target: None } => &["VimObject"],
Operator::FindForward { .. }
| Operator::Mark
| Operator::Jump { .. }
| Operator::FindBackward { .. }
| Operator::Replace
| Operator::AddSurrounds { target: Some(_) }
Expand Down
17 changes: 17 additions & 0 deletions crates/vim/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
8 changes: 7 additions & 1 deletion crates/vim/src/vim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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),
_ => {}
Expand Down
15 changes: 15 additions & 0 deletions crates/vim/test_data/test_marks.json
Original file line number Diff line number Diff line change
@@ -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"}}

0 comments on commit 8566b64

Please sign in to comment.