Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vim: Add basic mark support #11507

Merged
merged 4 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
13 changes: 11 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 @@ -469,6 +473,7 @@ impl Motion {
| WindowTop
| WindowMiddle
| WindowBottom
| Jump { line: true, .. }
| EndOfParagraph => true,
EndOfLine { .. }
| Matching
Expand All @@ -492,6 +497,7 @@ impl Motion {
| FindBackward { .. }
| RepeatFind { .. }
| RepeatFindReversed { .. }
| Jump { line: false, .. }
| ZedSearchResult { .. } => false,
}
}
Expand Down Expand Up @@ -531,7 +537,8 @@ impl Motion {
| WindowMiddle
| WindowBottom
| NextLineStart
| ZedSearchResult { .. } => false,
| ZedSearchResult { .. }
| Jump { .. } => false,
}
}

Expand Down Expand Up @@ -570,6 +577,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 +769,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
143 changes: 143 additions & 0 deletions crates/vim/src/normal/mark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use std::{ops::Range, sync::Arc};

use collections::HashSet;
use editor::{
display_map::{DisplaySnapshot, ToDisplayPoint},
movement, Anchor, Bias, DisplayPoint,
};
use gpui::WindowContext;
use language::SelectionGoal;

use crate::{
motion::{self, Motion},
Vim,
};

pub fn create_mark(vim: &mut Vim, text: Arc<str>, tail: bool, cx: &mut WindowContext) {
let Some(anchors) = vim.update_active_editor(cx, |_, editor, _| {
editor
.selections
.disjoint_anchors()
.iter()
.map(|s| if tail { s.tail() } else { s.head() })
.collect::<Vec<_>>()
}) else {
return;
};

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

pub fn create_mark_after(vim: &mut Vim, text: Arc<str>, 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::<Vec<_>>()
}) 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<str>, 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::<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 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::<Vec<Anchor>>()
})
}),
_ => 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() {
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))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Zachiah to fix the scrolling issue, this None needs to be Some(AutoScroll::fit())

});
})
}
}

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
Loading
Loading