mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 02:17:35 +03:00
vim: Add basic mark support (#11507)
Release Notes: - vim: Added support for buffer-local marks (`'a-'z`) and some builtin marks `'<`,`'>`,`'[`,`']`, `'{`, `'}` and `^`. Global marks (`'A-'Z`), and other builtin marks (`'0-'9`, `'(`, `')`, `''`, `'.`, `'"`) are not yet implemented. (#5122) --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
9cef0ac869
commit
901cb8b3d2
@ -117,6 +117,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"m": ["vim::PushOperator", "Mark"],
|
||||||
|
"'": ["vim::PushOperator", { "Jump": { "line": true } }],
|
||||||
|
"`": ["vim::PushOperator", { "Jump": { "line": false } }],
|
||||||
";": "vim::RepeatFind",
|
";": "vim::RepeatFind",
|
||||||
",": "vim::RepeatFindReversed",
|
",": "vim::RepeatFindReversed",
|
||||||
"ctrl-o": "pane::GoBack",
|
"ctrl-o": "pane::GoBack",
|
||||||
|
@ -13,7 +13,7 @@ use std::ops::Range;
|
|||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
normal::normal_motion,
|
normal::{mark, normal_motion},
|
||||||
state::{Mode, Operator},
|
state::{Mode, Operator},
|
||||||
surrounds::SurroundsType,
|
surrounds::SurroundsType,
|
||||||
utils::coerce_punctuation,
|
utils::coerce_punctuation,
|
||||||
@ -105,6 +105,10 @@ pub enum Motion {
|
|||||||
prior_selections: Vec<Range<Anchor>>,
|
prior_selections: Vec<Range<Anchor>>,
|
||||||
new_selections: Vec<Range<Anchor>>,
|
new_selections: Vec<Range<Anchor>>,
|
||||||
},
|
},
|
||||||
|
Jump {
|
||||||
|
anchor: Anchor,
|
||||||
|
line: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, PartialEq)]
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
@ -469,6 +473,7 @@ impl Motion {
|
|||||||
| WindowTop
|
| WindowTop
|
||||||
| WindowMiddle
|
| WindowMiddle
|
||||||
| WindowBottom
|
| WindowBottom
|
||||||
|
| Jump { line: true, .. }
|
||||||
| EndOfParagraph => true,
|
| EndOfParagraph => true,
|
||||||
EndOfLine { .. }
|
EndOfLine { .. }
|
||||||
| Matching
|
| Matching
|
||||||
@ -492,6 +497,7 @@ impl Motion {
|
|||||||
| FindBackward { .. }
|
| FindBackward { .. }
|
||||||
| RepeatFind { .. }
|
| RepeatFind { .. }
|
||||||
| RepeatFindReversed { .. }
|
| RepeatFindReversed { .. }
|
||||||
|
| Jump { line: false, .. }
|
||||||
| ZedSearchResult { .. } => false,
|
| ZedSearchResult { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -531,7 +537,8 @@ impl Motion {
|
|||||||
| WindowMiddle
|
| WindowMiddle
|
||||||
| WindowBottom
|
| WindowBottom
|
||||||
| NextLineStart
|
| NextLineStart
|
||||||
| ZedSearchResult { .. } => false,
|
| ZedSearchResult { .. }
|
||||||
|
| Jump { .. } => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,6 +577,7 @@ impl Motion {
|
|||||||
| PreviousSubwordStart { .. }
|
| PreviousSubwordStart { .. }
|
||||||
| FirstNonWhitespace { .. }
|
| FirstNonWhitespace { .. }
|
||||||
| FindBackward { .. }
|
| FindBackward { .. }
|
||||||
|
| Jump { .. }
|
||||||
| ZedSearchResult { .. } => false,
|
| ZedSearchResult { .. } => false,
|
||||||
RepeatFind { last_find: motion } | RepeatFindReversed { last_find: motion } => {
|
RepeatFind { last_find: motion } | RepeatFindReversed { last_find: motion } => {
|
||||||
motion.inclusive()
|
motion.inclusive()
|
||||||
@ -761,6 +769,7 @@ impl Motion {
|
|||||||
WindowTop => window_top(map, point, &text_layout_details, times - 1),
|
WindowTop => window_top(map, point, &text_layout_details, times - 1),
|
||||||
WindowMiddle => window_middle(map, point, &text_layout_details),
|
WindowMiddle => window_middle(map, point, &text_layout_details),
|
||||||
WindowBottom => window_bottom(map, point, &text_layout_details, times - 1),
|
WindowBottom => window_bottom(map, point, &text_layout_details, times - 1),
|
||||||
|
Jump { line, anchor } => mark::jump_motion(map, *anchor, *line),
|
||||||
ZedSearchResult { new_selections, .. } => {
|
ZedSearchResult { new_selections, .. } => {
|
||||||
// There will be only one selection, as
|
// There will be only one selection, as
|
||||||
// Search::SelectNextMatch selects a single match.
|
// Search::SelectNextMatch selects a single match.
|
||||||
|
@ -2,6 +2,7 @@ mod case;
|
|||||||
mod change;
|
mod change;
|
||||||
mod delete;
|
mod delete;
|
||||||
mod increment;
|
mod increment;
|
||||||
|
pub(crate) mod mark;
|
||||||
mod paste;
|
mod paste;
|
||||||
pub(crate) mod repeat;
|
pub(crate) mod repeat;
|
||||||
mod scroll;
|
mod scroll;
|
||||||
|
147
crates/vim/src/normal/mark.rs
Normal file
147
crates/vim/src/normal/mark.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
use std::{ops::Range, sync::Arc};
|
||||||
|
|
||||||
|
use editor::{
|
||||||
|
display_map::{DisplaySnapshot, ToDisplayPoint},
|
||||||
|
movement,
|
||||||
|
scroll::Autoscroll,
|
||||||
|
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: Vec<Range<Anchor>> = Vec::new();
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
if ranges.last() != Some(&(anchor..anchor)) {
|
||||||
|
ranges.push(anchor..anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editor.change_selections(Some(Autoscroll::fit()), 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)
|
||||||
|
}
|
@ -59,6 +59,8 @@ pub enum Operator {
|
|||||||
AddSurrounds { target: Option<SurroundsType> },
|
AddSurrounds { target: Option<SurroundsType> },
|
||||||
ChangeSurrounds { target: Option<Object> },
|
ChangeSurrounds { target: Option<Object> },
|
||||||
DeleteSurrounds,
|
DeleteSurrounds,
|
||||||
|
Mark,
|
||||||
|
Jump { line: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
@ -74,6 +76,8 @@ pub struct EditorState {
|
|||||||
pub operator_stack: Vec<Operator>,
|
pub operator_stack: Vec<Operator>,
|
||||||
pub replacements: Vec<(Range<editor::Anchor>, String)>,
|
pub replacements: Vec<(Range<editor::Anchor>, String)>,
|
||||||
|
|
||||||
|
pub marks: HashMap<String, Vec<Anchor>>,
|
||||||
|
|
||||||
pub current_tx: Option<TransactionId>,
|
pub current_tx: Option<TransactionId>,
|
||||||
pub current_anchor: Option<Selection<Anchor>>,
|
pub current_anchor: Option<Selection<Anchor>>,
|
||||||
pub undo_modes: HashMap<TransactionId, Mode>,
|
pub undo_modes: HashMap<TransactionId, Mode>,
|
||||||
@ -172,7 +176,10 @@ impl EditorState {
|
|||||||
}
|
}
|
||||||
matches!(
|
matches!(
|
||||||
self.operator_stack.last(),
|
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::AddSurrounds { .. } => "ys",
|
||||||
Operator::ChangeSurrounds { .. } => "cs",
|
Operator::ChangeSurrounds { .. } => "cs",
|
||||||
Operator::DeleteSurrounds => "ds",
|
Operator::DeleteSurrounds => "ds",
|
||||||
|
Operator::Mark => "m",
|
||||||
|
Operator::Jump { line: true } => "'",
|
||||||
|
Operator::Jump { line: false } => "`",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,6 +271,8 @@ impl Operator {
|
|||||||
match self {
|
match self {
|
||||||
Operator::Object { .. } | Operator::ChangeSurrounds { target: None } => &["VimObject"],
|
Operator::Object { .. } | Operator::ChangeSurrounds { target: None } => &["VimObject"],
|
||||||
Operator::FindForward { .. }
|
Operator::FindForward { .. }
|
||||||
|
| Operator::Mark
|
||||||
|
| Operator::Jump { .. }
|
||||||
| Operator::FindBackward { .. }
|
| Operator::FindBackward { .. }
|
||||||
| Operator::Replace
|
| Operator::Replace
|
||||||
| Operator::AddSurrounds { target: Some(_) }
|
| Operator::AddSurrounds { target: Some(_) }
|
||||||
|
@ -1073,3 +1073,133 @@ async fn test_mouse_selection(cx: &mut TestAppContext) {
|
|||||||
|
|
||||||
cx.assert_state("one «ˇtwo» three", Mode::Visual)
|
cx.assert_state("one «ˇtwo» three", Mode::Visual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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;
|
||||||
|
}
|
||||||
|
@ -39,6 +39,24 @@ fn copy_selections_content_internal(
|
|||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
let mut clipboard_selections = Vec::with_capacity(selections.len());
|
let mut clipboard_selections = Vec::with_capacity(selections.len());
|
||||||
let mut ranges_to_highlight = Vec::new();
|
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;
|
let mut is_first = true;
|
||||||
for selection in selections.iter() {
|
for selection in selections.iter() {
|
||||||
|
@ -30,7 +30,10 @@ use gpui::{
|
|||||||
use language::{CursorShape, Point, SelectionGoal, TransactionId};
|
use language::{CursorShape, Point, SelectionGoal, TransactionId};
|
||||||
pub use mode_indicator::ModeIndicator;
|
pub use mode_indicator::ModeIndicator;
|
||||||
use motion::Motion;
|
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 replace::multi_replace;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@ -194,7 +197,9 @@ fn observe_keystrokes(keystroke_event: &KeystrokeEvent, cx: &mut WindowContext)
|
|||||||
| Operator::Replace
|
| Operator::Replace
|
||||||
| Operator::AddSurrounds { .. }
|
| Operator::AddSurrounds { .. }
|
||||||
| Operator::ChangeSurrounds { .. }
|
| Operator::ChangeSurrounds { .. }
|
||||||
| Operator::DeleteSurrounds,
|
| Operator::DeleteSurrounds
|
||||||
|
| Operator::Mark
|
||||||
|
| Operator::Jump { .. },
|
||||||
) => {}
|
) => {}
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
vim.clear_operator(cx);
|
vim.clear_operator(cx);
|
||||||
@ -418,6 +423,10 @@ impl Vim {
|
|||||||
// Sync editor settings like clip mode
|
// Sync editor settings like clip mode
|
||||||
self.sync_vim_settings(cx);
|
self.sync_vim_settings(cx);
|
||||||
|
|
||||||
|
if mode != Mode::Insert && last_mode == Mode::Insert {
|
||||||
|
create_mark_after(self, "^".into(), cx)
|
||||||
|
}
|
||||||
|
|
||||||
if leave_selections {
|
if leave_selections {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -614,6 +623,7 @@ impl Vim {
|
|||||||
let is_multicursor = editor.read(cx).selections.count() > 1;
|
let is_multicursor = editor.read(cx).selections.count() > 1;
|
||||||
|
|
||||||
let state = self.state();
|
let state = self.state();
|
||||||
|
let mut is_visual = state.mode.is_visual();
|
||||||
if state.mode == Mode::Insert && state.current_tx.is_some() {
|
if state.mode == Mode::Insert && state.current_tx.is_some() {
|
||||||
if state.current_anchor.is_none() {
|
if state.current_anchor.is_none() {
|
||||||
self.update_state(|state| state.current_anchor = Some(newest));
|
self.update_state(|state| state.current_anchor = Some(newest));
|
||||||
@ -630,11 +640,18 @@ impl Vim {
|
|||||||
} else {
|
} else {
|
||||||
self.switch_mode(Mode::Visual, false, cx)
|
self.switch_mode(Mode::Visual, false, cx)
|
||||||
}
|
}
|
||||||
|
is_visual = true;
|
||||||
} else if newest.start == newest.end
|
} else if newest.start == newest.end
|
||||||
&& !is_multicursor
|
&& !is_multicursor
|
||||||
&& [Mode::Visual, Mode::VisualLine, Mode::VisualBlock].contains(&state.mode)
|
&& [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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -706,6 +723,10 @@ impl Vim {
|
|||||||
}
|
}
|
||||||
_ => Vim::update(cx, |vim, cx| vim.clear_operator(cx)),
|
_ => Vim::update(cx, |vim, cx| vim.clear_operator(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 {
|
_ => match Vim::read(cx).state().mode {
|
||||||
Mode::Replace => multi_replace(text, cx),
|
Mode::Replace => multi_replace(text, cx),
|
||||||
_ => {}
|
_ => {}
|
||||||
|
36
crates/vim/test_data/test_builtin_marks.json
Normal file
36
crates/vim/test_data/test_builtin_marks.json
Normal file
@ -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"}}
|
26
crates/vim/test_data/test_caret_mark.json
Normal file
26
crates/vim/test_data/test_caret_mark.json
Normal file
@ -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"}}
|
15
crates/vim/test_data/test_lowercase_marks.json
Normal file
15
crates/vim/test_data/test_lowercase_marks.json
Normal file
@ -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"}}
|
18
crates/vim/test_data/test_lt_gt_marks.json
Normal file
18
crates/vim/test_data/test_lt_gt_marks.json
Normal file
@ -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"}}
|
15
crates/vim/test_data/test_marks.json
Normal file
15
crates/vim/test_data/test_marks.json
Normal file
@ -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"}}
|
14
crates/vim/test_data/test_period_mark.json
Normal file
14
crates/vim/test_data/test_period_mark.json
Normal file
@ -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"}}
|
Loading…
Reference in New Issue
Block a user