vim: Replace with Register (#24326)

Closes #18813

Release Notes:

- vim: Add `gr` for [replace with
register](https://github.com/vim-scripts/ReplaceWithRegister)
This commit is contained in:
Conrad Irwin 2025-02-06 20:24:41 -07:00 committed by GitHub
parent d83c316e6d
commit 8646d37c0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 120 additions and 1 deletions

View file

@ -102,6 +102,7 @@
"ctrl-e": "vim::LineDown",
"ctrl-y": "vim::LineUp",
// "g" commands
"g r": ["vim::PushOperator", "ReplaceWithRegister"],
"g g": "vim::StartOfDocument",
"g h": "editor::Hover",
"g t": "pane::ActivateNextItem",

View file

@ -182,6 +182,9 @@ impl Vim {
Some(Operator::ToggleComments) => {
self.toggle_comments_motion(motion, times, window, cx)
}
Some(Operator::ReplaceWithRegister) => {
self.replace_with_register_motion(motion, times, window, cx)
}
Some(operator) => {
// Can't do anything for text objects, Ignoring
error!("Unexpected normal mode motion operator: {:?}", operator)
@ -228,6 +231,9 @@ impl Vim {
Some(Operator::ToggleComments) => {
self.toggle_comments_object(object, around, window, cx)
}
Some(Operator::ReplaceWithRegister) => {
self.replace_with_register_object(object, around, window, cx)
}
_ => {
// Can't do anything for namespace operators. Ignoring
}

View file

@ -6,6 +6,8 @@ use serde::Deserialize;
use std::cmp;
use crate::{
motion::Motion,
object::Object,
state::{Mode, Register},
Vim,
};
@ -192,12 +194,85 @@ impl Vim {
});
self.switch_mode(Mode::Normal, true, window, cx);
}
pub fn replace_with_register_object(
&mut self,
object: Object,
around: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.stop_recording(cx);
let selected_register = self.selected_register.take();
self.update_editor(window, cx, |_, editor, window, cx| {
editor.transact(window, cx, |editor, window, cx| {
editor.set_clip_at_line_ends(false, cx);
editor.change_selections(None, window, cx, |s| {
s.move_with(|map, selection| {
object.expand_selection(map, selection, around);
});
});
let Some(Register { text, .. }) = Vim::update_globals(cx, |globals, cx| {
globals.read_register(selected_register, Some(editor), cx)
})
.filter(|reg| !reg.text.is_empty()) else {
return;
};
editor.insert(&text, window, cx);
editor.set_clip_at_line_ends(true, cx);
editor.change_selections(None, window, cx, |s| {
s.move_with(|map, selection| {
selection.start = map.clip_point(selection.start, Bias::Left);
selection.end = selection.start
})
})
});
});
}
pub fn replace_with_register_motion(
&mut self,
motion: Motion,
times: Option<usize>,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.stop_recording(cx);
let selected_register = self.selected_register.take();
self.update_editor(window, cx, |_, editor, window, cx| {
let text_layout_details = editor.text_layout_details(window);
editor.transact(window, cx, |editor, window, cx| {
editor.set_clip_at_line_ends(false, cx);
editor.change_selections(None, window, cx, |s| {
s.move_with(|map, selection| {
motion.expand_selection(map, selection, times, false, &text_layout_details);
});
});
let Some(Register { text, .. }) = Vim::update_globals(cx, |globals, cx| {
globals.read_register(selected_register, Some(editor), cx)
})
.filter(|reg| !reg.text.is_empty()) else {
return;
};
editor.insert(&text, window, cx);
editor.set_clip_at_line_ends(true, cx);
editor.change_selections(None, window, cx, |s| {
s.move_with(|map, selection| {
selection.start = map.clip_point(selection.start, Bias::Left);
selection.end = selection.start
})
})
});
});
}
}
#[cfg(test)]
mod test {
use crate::{
state::Mode,
state::{Mode, Register},
test::{NeovimBackedTestContext, VimTestContext},
UseSystemClipboard, VimSettings,
};
@ -742,4 +817,37 @@ mod test {
Mode::Normal,
);
}
#[gpui::test]
async fn test_replace_with_register(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
cx.set_state(
indoc! {"
ˇfish one
two three
"},
Mode::Normal,
);
cx.simulate_keystrokes("y i w");
cx.simulate_keystrokes("w");
cx.simulate_keystrokes("g r i w");
cx.assert_state(
indoc! {"
fish fisˇh
two three
"},
Mode::Normal,
);
cx.simulate_keystrokes("j b g r e");
cx.assert_state(
indoc! {"
fish fish
two fisˇh
"},
Mode::Normal,
);
let clipboard: Register = cx.read_from_clipboard().unwrap().into();
assert_eq!(clipboard.text, "fish");
}
}

View file

@ -111,6 +111,7 @@ pub enum Operator {
RecordRegister,
ReplayRegister,
ToggleComments,
ReplaceWithRegister,
}
#[derive(Default, Clone, Debug)]
@ -499,6 +500,7 @@ impl Operator {
Operator::AutoIndent => "eq",
Operator::ShellCommand => "sh",
Operator::Rewrap => "gq",
Operator::ReplaceWithRegister => "gr",
Operator::Outdent => "<",
Operator::Uppercase => "gU",
Operator::Lowercase => "gu",
@ -551,6 +553,7 @@ impl Operator {
| Operator::ShellCommand
| Operator::Lowercase
| Operator::Uppercase
| Operator::ReplaceWithRegister
| Operator::Object { .. }
| Operator::ChangeSurrounds { target: None }
| Operator::OppositeCase

View file

@ -159,6 +159,7 @@ Zed's vim mode includes some features that are usually provided by very popular
- You can comment and uncomment selections with `gc` in visual mode and `gcc` in normal mode.
- The project panel supports many shortcuts modeled after the Vim plugin `netrw`: navigation with `hjkl`, open file with `o`, open file in a new tab with `t`, etc.
- You can add key bindings to your keymap to navigate "camelCase" names. [Head down to the Optional key bindings](#optional-key-bindings) section to learn how.
- You can use `gr` to do [ReplaceWithRegister](https://github.com/vim-scripts/ReplaceWithRegister).
## Command palette