vim: Better jump list support (#33495)
Closes #23527 Closes #30183 Closes some Discord chats Release Notes: - vim: Motions now push to the jump list using the same logic as vim (i.e. `G`/`g g`/`g d` always do, but `j`/`k` always don't). Most non-vim actions (including clicking with the mouse) continue to push to the jump list only when they move the cursor by 10 or more lines.
This commit is contained in:
parent
ba1c05abf2
commit
20a3e613b8
5 changed files with 147 additions and 19 deletions
|
@ -1256,7 +1256,7 @@ impl Default for SelectionHistoryMode {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SelectionEffects {
|
pub struct SelectionEffects {
|
||||||
nav_history: bool,
|
nav_history: Option<bool>,
|
||||||
completions: bool,
|
completions: bool,
|
||||||
scroll: Option<Autoscroll>,
|
scroll: Option<Autoscroll>,
|
||||||
}
|
}
|
||||||
|
@ -1264,7 +1264,7 @@ pub struct SelectionEffects {
|
||||||
impl Default for SelectionEffects {
|
impl Default for SelectionEffects {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
nav_history: true,
|
nav_history: None,
|
||||||
completions: true,
|
completions: true,
|
||||||
scroll: Some(Autoscroll::fit()),
|
scroll: Some(Autoscroll::fit()),
|
||||||
}
|
}
|
||||||
|
@ -1294,7 +1294,7 @@ impl SelectionEffects {
|
||||||
|
|
||||||
pub fn nav_history(self, nav_history: bool) -> Self {
|
pub fn nav_history(self, nav_history: bool) -> Self {
|
||||||
Self {
|
Self {
|
||||||
nav_history,
|
nav_history: Some(nav_history),
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2909,11 +2909,12 @@ impl Editor {
|
||||||
let new_cursor_position = newest_selection.head();
|
let new_cursor_position = newest_selection.head();
|
||||||
let selection_start = newest_selection.start;
|
let selection_start = newest_selection.start;
|
||||||
|
|
||||||
if effects.nav_history {
|
if effects.nav_history.is_none() || effects.nav_history == Some(true) {
|
||||||
self.push_to_nav_history(
|
self.push_to_nav_history(
|
||||||
*old_cursor_position,
|
*old_cursor_position,
|
||||||
Some(new_cursor_position.to_point(buffer)),
|
Some(new_cursor_position.to_point(buffer)),
|
||||||
false,
|
false,
|
||||||
|
effects.nav_history == Some(true),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3164,7 +3165,7 @@ impl Editor {
|
||||||
if let Some(state) = &mut self.deferred_selection_effects_state {
|
if let Some(state) = &mut self.deferred_selection_effects_state {
|
||||||
state.effects.scroll = effects.scroll.or(state.effects.scroll);
|
state.effects.scroll = effects.scroll.or(state.effects.scroll);
|
||||||
state.effects.completions = effects.completions;
|
state.effects.completions = effects.completions;
|
||||||
state.effects.nav_history |= effects.nav_history;
|
state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
|
||||||
let (changed, result) = self.selections.change_with(cx, change);
|
let (changed, result) = self.selections.change_with(cx, change);
|
||||||
state.changed |= changed;
|
state.changed |= changed;
|
||||||
return result;
|
return result;
|
||||||
|
@ -13097,7 +13098,13 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
|
pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
|
||||||
self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
|
self.push_to_nav_history(
|
||||||
|
self.selections.newest_anchor().head(),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_to_nav_history(
|
fn push_to_nav_history(
|
||||||
|
@ -13105,6 +13112,7 @@ impl Editor {
|
||||||
cursor_anchor: Anchor,
|
cursor_anchor: Anchor,
|
||||||
new_position: Option<Point>,
|
new_position: Option<Point>,
|
||||||
is_deactivate: bool,
|
is_deactivate: bool,
|
||||||
|
always: bool,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
if let Some(nav_history) = self.nav_history.as_mut() {
|
if let Some(nav_history) = self.nav_history.as_mut() {
|
||||||
|
@ -13116,7 +13124,7 @@ impl Editor {
|
||||||
|
|
||||||
if let Some(new_position) = new_position {
|
if let Some(new_position) = new_position {
|
||||||
let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
|
let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
|
||||||
if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
|
if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14788,9 +14796,12 @@ impl Editor {
|
||||||
let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
|
let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
|
self.change_selections(
|
||||||
s.select_anchor_ranges([start..end])
|
SelectionEffects::default().nav_history(true),
|
||||||
});
|
window,
|
||||||
|
cx,
|
||||||
|
|s| s.select_anchor_ranges([start..end]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn go_to_diagnostic(
|
pub fn go_to_diagnostic(
|
||||||
|
|
|
@ -778,7 +778,7 @@ impl Item for Editor {
|
||||||
|
|
||||||
fn deactivated(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
fn deactivated(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
let selection = self.selections.newest_anchor();
|
let selection = self.selections.newest_anchor();
|
||||||
self.push_to_nav_history(selection.head(), None, true, cx);
|
self.push_to_nav_history(selection.head(), None, true, false, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workspace_deactivated(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
fn workspace_deactivated(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
|
|
@ -768,6 +768,73 @@ impl Motion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn push_to_jump_list(&self) -> bool {
|
||||||
|
use Motion::*;
|
||||||
|
match self {
|
||||||
|
CurrentLine
|
||||||
|
| Down { .. }
|
||||||
|
| EndOfLine { .. }
|
||||||
|
| EndOfLineDownward
|
||||||
|
| FindBackward { .. }
|
||||||
|
| FindForward { .. }
|
||||||
|
| FirstNonWhitespace { .. }
|
||||||
|
| GoToColumn
|
||||||
|
| Left
|
||||||
|
| MiddleOfLine { .. }
|
||||||
|
| NextLineStart
|
||||||
|
| NextSubwordEnd { .. }
|
||||||
|
| NextSubwordStart { .. }
|
||||||
|
| NextWordEnd { .. }
|
||||||
|
| NextWordStart { .. }
|
||||||
|
| PreviousLineStart
|
||||||
|
| PreviousSubwordEnd { .. }
|
||||||
|
| PreviousSubwordStart { .. }
|
||||||
|
| PreviousWordEnd { .. }
|
||||||
|
| PreviousWordStart { .. }
|
||||||
|
| RepeatFind { .. }
|
||||||
|
| RepeatFindReversed { .. }
|
||||||
|
| Right
|
||||||
|
| StartOfLine { .. }
|
||||||
|
| StartOfLineDownward
|
||||||
|
| Up { .. }
|
||||||
|
| WrappingLeft
|
||||||
|
| WrappingRight => false,
|
||||||
|
EndOfDocument
|
||||||
|
| EndOfParagraph
|
||||||
|
| GoToPercentage
|
||||||
|
| Jump { .. }
|
||||||
|
| Matching
|
||||||
|
| NextComment
|
||||||
|
| NextGreaterIndent
|
||||||
|
| NextLesserIndent
|
||||||
|
| NextMethodEnd
|
||||||
|
| NextMethodStart
|
||||||
|
| NextSameIndent
|
||||||
|
| NextSectionEnd
|
||||||
|
| NextSectionStart
|
||||||
|
| PreviousComment
|
||||||
|
| PreviousGreaterIndent
|
||||||
|
| PreviousLesserIndent
|
||||||
|
| PreviousMethodEnd
|
||||||
|
| PreviousMethodStart
|
||||||
|
| PreviousSameIndent
|
||||||
|
| PreviousSectionEnd
|
||||||
|
| PreviousSectionStart
|
||||||
|
| SentenceBackward
|
||||||
|
| SentenceForward
|
||||||
|
| Sneak { .. }
|
||||||
|
| SneakBackward { .. }
|
||||||
|
| StartOfDocument
|
||||||
|
| StartOfParagraph
|
||||||
|
| UnmatchedBackward { .. }
|
||||||
|
| UnmatchedForward { .. }
|
||||||
|
| WindowBottom
|
||||||
|
| WindowMiddle
|
||||||
|
| WindowTop
|
||||||
|
| ZedSearchResult { .. } => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn infallible(&self) -> bool {
|
pub fn infallible(&self) -> bool {
|
||||||
use Motion::*;
|
use Motion::*;
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -24,10 +24,10 @@ use crate::{
|
||||||
};
|
};
|
||||||
use collections::BTreeSet;
|
use collections::BTreeSet;
|
||||||
use convert::ConvertTarget;
|
use convert::ConvertTarget;
|
||||||
use editor::Anchor;
|
|
||||||
use editor::Bias;
|
use editor::Bias;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use editor::scroll::Autoscroll;
|
use editor::scroll::Autoscroll;
|
||||||
|
use editor::{Anchor, SelectionEffects};
|
||||||
use editor::{display_map::ToDisplayPoint, movement};
|
use editor::{display_map::ToDisplayPoint, movement};
|
||||||
use gpui::{Context, Window, actions};
|
use gpui::{Context, Window, actions};
|
||||||
use language::{Point, SelectionGoal, ToPoint};
|
use language::{Point, SelectionGoal, ToPoint};
|
||||||
|
@ -358,13 +358,18 @@ impl Vim {
|
||||||
) {
|
) {
|
||||||
self.update_editor(window, cx, |_, editor, window, cx| {
|
self.update_editor(window, cx, |_, editor, window, cx| {
|
||||||
let text_layout_details = editor.text_layout_details(window);
|
let text_layout_details = editor.text_layout_details(window);
|
||||||
editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
|
editor.change_selections(
|
||||||
s.move_cursors_with(|map, cursor, goal| {
|
SelectionEffects::default().nav_history(motion.push_to_jump_list()),
|
||||||
motion
|
window,
|
||||||
.move_point(map, cursor, goal, times, &text_layout_details)
|
cx,
|
||||||
.unwrap_or((cursor, goal))
|
|s| {
|
||||||
})
|
s.move_cursors_with(|map, cursor, goal| {
|
||||||
})
|
motion
|
||||||
|
.move_point(map, cursor, goal, times, &text_layout_details)
|
||||||
|
.unwrap_or((cursor, goal))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1799,4 +1804,35 @@ mod test {
|
||||||
fox jˇumps over
|
fox jˇumps over
|
||||||
the lazy dog"});
|
the lazy dog"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_jump_list(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||||
|
|
||||||
|
cx.set_shared_state(indoc! {"
|
||||||
|
ˇfn a() { }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fn b() { }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fn b() { }"})
|
||||||
|
.await;
|
||||||
|
cx.simulate_shared_keystrokes("3 }").await;
|
||||||
|
cx.shared_state().await.assert_matches();
|
||||||
|
cx.simulate_shared_keystrokes("ctrl-o").await;
|
||||||
|
cx.shared_state().await.assert_matches();
|
||||||
|
cx.simulate_shared_keystrokes("ctrl-i").await;
|
||||||
|
cx.shared_state().await.assert_matches();
|
||||||
|
cx.simulate_shared_keystrokes("1 1 k").await;
|
||||||
|
cx.shared_state().await.assert_matches();
|
||||||
|
cx.simulate_shared_keystrokes("ctrl-o").await;
|
||||||
|
cx.shared_state().await.assert_matches();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
14
crates/vim/test_data/test_jump_list.json
Normal file
14
crates/vim/test_data/test_jump_list.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{"Put":{"state":"ˇfn a() { }\n\n\n\n\n\nfn b() { }\n\n\n\n\n\nfn b() { }"}}
|
||||||
|
{"Key":"3"}
|
||||||
|
{"Key":"}"}
|
||||||
|
{"Get":{"state":"fn a() { }\n\n\n\n\n\nfn b() { }\n\n\n\n\n\nfn b() { ˇ}","mode":"Normal"}}
|
||||||
|
{"Key":"ctrl-o"}
|
||||||
|
{"Get":{"state":"ˇfn a() { }\n\n\n\n\n\nfn b() { }\n\n\n\n\n\nfn b() { }","mode":"Normal"}}
|
||||||
|
{"Key":"ctrl-i"}
|
||||||
|
{"Get":{"state":"fn a() { }\n\n\n\n\n\nfn b() { }\n\n\n\n\n\nfn b() { ˇ}","mode":"Normal"}}
|
||||||
|
{"Key":"1"}
|
||||||
|
{"Key":"1"}
|
||||||
|
{"Key":"k"}
|
||||||
|
{"Get":{"state":"fn a() { }\nˇ\n\n\n\n\nfn b() { }\n\n\n\n\n\nfn b() { }","mode":"Normal"}}
|
||||||
|
{"Key":"ctrl-o"}
|
||||||
|
{"Get":{"state":"ˇfn a() { }\n\n\n\n\n\nfn b() { }\n\n\n\n\n\nfn b() { }","mode":"Normal"}}
|
Loading…
Add table
Add a link
Reference in a new issue