From 3975d8ea931e2834e3c018ec873b73a8aad2f0ca Mon Sep 17 00:00:00 2001 From: Asqar Arslanov Date: Wed, 5 Mar 2025 18:54:30 +0300 Subject: [PATCH] vim: Rename wrapping keybindings + document cursor wrapping (#25694) https://github.com/zed-industries/zed/pull/25663#issuecomment-2686095807 Renamed the `vim::Backspace` and `vim::Space` actions to `vim::WrappingLeft` and `vim::WrappingRight` respectively. The old names are still available, but they are marked as deprecated and users are advised to use the new names. Also added a paragraph to the docs describing how to enable wrapping cursor navigation. --- assets/keymaps/vim.json | 4 +-- crates/vim/src/motion.rs | 47 ++++++++++++++++++++------------- crates/vim/src/normal/change.rs | 2 +- crates/vim/src/replace.rs | 2 +- docs/src/vim.md | 16 +++++++++++ 5 files changed, 49 insertions(+), 22 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index ac0caf9167f4dae322d124b24f0fa86b7a73d2c2..e99eeed273a67b9a113bc2cdf1de19d21ce4ee20 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -6,7 +6,7 @@ "a": ["vim::PushObject", { "around": true }], "left": "vim::Left", "h": "vim::Left", - "backspace": "vim::Backspace", + "backspace": "vim::WrappingLeft", "down": "vim::Down", "ctrl-j": "vim::Down", "j": "vim::Down", @@ -20,7 +20,7 @@ "k": "vim::Up", "right": "vim::Right", "l": "vim::Right", - "space": "vim::Space", + "space": "vim::WrappingRight", "end": "vim::EndOfLine", "$": "vim::EndOfLine", "^": "vim::FirstNonWhitespace", diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 7f3b831f0894cf6cfc2f809de579288320afa03d..55614a10bba3bfdc394469feb120a10fa7998980 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -6,7 +6,7 @@ use editor::{ scroll::Autoscroll, Anchor, Bias, DisplayPoint, Editor, RowExt, ToOffset, ToPoint, }; -use gpui::{actions, impl_actions, px, Context, Window}; +use gpui::{action_with_deprecated_aliases, actions, impl_actions, px, Context, Window}; use language::{CharKind, Point, Selection, SelectionGoal}; use multi_buffer::MultiBufferRow; use schemars::JsonSchema; @@ -24,7 +24,7 @@ use crate::{ #[derive(Clone, Debug, PartialEq, Eq)] pub enum Motion { Left, - Backspace, + WrappingLeft, Down { display_lines: bool, }, @@ -32,7 +32,7 @@ pub enum Motion { display_lines: bool, }, Right, - Space, + WrappingRight, NextWordStart { ignore_punctuation: bool, }, @@ -304,12 +304,19 @@ actions!( ] ); +action_with_deprecated_aliases!(vim, WrappingLeft, ["vim::Backspace"]); +action_with_deprecated_aliases!(vim, WrappingRight, ["vim::Space"]); + pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, |vim, _: &Left, window, cx| { vim.motion(Motion::Left, window, cx) }); + Vim::action(editor, cx, |vim, _: &WrappingLeft, window, cx| { + vim.motion(Motion::WrappingLeft, window, cx) + }); + // Deprecated. Vim::action(editor, cx, |vim, _: &Backspace, window, cx| { - vim.motion(Motion::Backspace, window, cx) + vim.motion(Motion::WrappingLeft, window, cx) }); Vim::action(editor, cx, |vim, action: &Down, window, cx| { vim.motion( @@ -332,8 +339,12 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { Vim::action(editor, cx, |vim, _: &Right, window, cx| { vim.motion(Motion::Right, window, cx) }); + Vim::action(editor, cx, |vim, _: &WrappingRight, window, cx| { + vim.motion(Motion::WrappingRight, window, cx) + }); + // Deprecated. Vim::action(editor, cx, |vim, _: &Space, window, cx| { - vim.motion(Motion::Space, window, cx) + vim.motion(Motion::WrappingRight, window, cx) }); Vim::action( editor, @@ -639,11 +650,11 @@ impl Motion { | UnmatchedBackward { .. } | FindForward { .. } | Left - | Backspace + | WrappingLeft | Right | SentenceBackward | SentenceForward - | Space + | WrappingRight | StartOfLine { .. } | EndOfLineDownward | GoToColumn @@ -679,9 +690,9 @@ impl Motion { | FindForward { .. } | RepeatFind { .. } | Left - | Backspace + | WrappingLeft | Right - | Space + | WrappingRight | StartOfLine { .. } | StartOfParagraph | EndOfParagraph @@ -747,9 +758,9 @@ impl Motion { | NextLineStart | PreviousLineStart => true, Left - | Backspace + | WrappingLeft | Right - | Space + | WrappingRight | StartOfLine { .. } | StartOfLineDownward | StartOfParagraph @@ -796,7 +807,7 @@ impl Motion { let infallible = self.infallible(); let (new_point, goal) = match self { Left => (left(map, point, times), SelectionGoal::None), - Backspace => (backspace(map, point, times), SelectionGoal::None), + WrappingLeft => (wrapping_left(map, point, times), SelectionGoal::None), Down { display_lines: false, } => up_down_buffer_rows(map, point, goal, times as isize, text_layout_details), @@ -810,7 +821,7 @@ impl Motion { display_lines: true, } => up_display(map, point, goal, times, text_layout_details), Right => (right(map, point, times), SelectionGoal::None), - Space => (space(map, point, times), SelectionGoal::None), + WrappingRight => (wrapping_right(map, point, times), SelectionGoal::None), NextWordStart { ignore_punctuation } => ( next_word_start(map, point, *ignore_punctuation, times), SelectionGoal::None, @@ -1219,7 +1230,7 @@ impl Motion { // DisplayPoint if !inclusive - && self != &Motion::Backspace + && self != &Motion::WrappingLeft && end_point.row > start_point.row && end_point.column == 0 { @@ -1274,7 +1285,7 @@ fn left(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> Display point } -pub(crate) fn backspace( +pub(crate) fn wrapping_left( map: &DisplaySnapshot, mut point: DisplayPoint, times: usize, @@ -1288,9 +1299,9 @@ pub(crate) fn backspace( point } -fn space(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint { +fn wrapping_right(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> DisplayPoint { for _ in 0..times { - point = wrapping_right(map, point); + point = wrapping_right_single(map, point); if point == map.max_point() { break; } @@ -1298,7 +1309,7 @@ fn space(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> Displa point } -fn wrapping_right(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { +fn wrapping_right_single(map: &DisplaySnapshot, mut point: DisplayPoint) -> DisplayPoint { let max_column = map.line_len(point.row()).saturating_sub(1); if point.column() < max_column { *point.column_mut() += 1; diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index 5ab18afe9c44cc26f38737f8f56de7de264e968e..1f7806055ae2077c145f380cafbb2f8d3d195240 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -27,7 +27,7 @@ impl Vim { Motion::Left | Motion::Right | Motion::EndOfLine { .. } - | Motion::Backspace + | Motion::WrappingLeft | Motion::StartOfLine { .. } ); self.update_editor(window, cx, |vim, editor, window, cx| { diff --git a/crates/vim/src/replace.rs b/crates/vim/src/replace.rs index ada0194f0e836fd891085a201b8aba1935c9a4f2..132487fb0e0d11dd7779fa7ecd32a2b9858a3ae4 100644 --- a/crates/vim/src/replace.rs +++ b/crates/vim/src/replace.rs @@ -95,7 +95,7 @@ impl Vim { .into_iter() .filter_map(|selection| { let end = selection.head(); - let start = motion::backspace( + let start = motion::wrapping_left( &map, end.to_display_point(&map), maybe_times.unwrap_or(1), diff --git a/docs/src/vim.md b/docs/src/vim.md index 52f00ca93fc494710c4aeefa2d8732f45c277313..61998e993067c421db7c0270e6e7943e2ee2a1ba 100644 --- a/docs/src/vim.md +++ b/docs/src/vim.md @@ -408,6 +408,22 @@ Vim mode comes with shortcuts to surround the selection in normal mode (`ys`), b } ``` +In non-modal text editors, cursor navigation typically wraps when moving past line ends. Zed, however, handles this behavior exactly like Vim by default: the cursor stops at line boundaries. If you prefer your cursor to wrap between lines, override these keybindings: + +```json +// In VimScript, this would look like this: +// set whichwrap+=<,>,[,],h,l +{ + "context": "VimControl && !menu", + "bindings": { + "left": "vim::WrappingLeft", + "right": "vim::WrappingRight", + "h": "vim::WrappingLeft", + "l": "vim::WrappingRight" + } +} +``` + The [Sneak motion](https://github.com/justinmk/vim-sneak) feature allows for quick navigation to any two-character sequence in your text. You can enable it by adding the following keybindings to your keymap. By default, the `s` key is mapped to `vim::Substitute`. Adding these bindings will override that behavior, so ensure this change aligns with your workflow preferences. ```json