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: Replace with Register #24326

Merged
merged 5 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions assets/keymaps/vim.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 6 additions & 0 deletions crates/vim/src/normal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
110 changes: 109 additions & 1 deletion crates/vim/src/normal/paste.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use serde::Deserialize;
use std::cmp;

use crate::{
motion::Motion,
object::Object,
state::{Mode, Register},
Vim,
};
Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -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");
}
}
3 changes: 3 additions & 0 deletions crates/vim/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ pub enum Operator {
RecordRegister,
ReplayRegister,
ToggleComments,
ReplaceWithRegister,
}

#[derive(Default, Clone, Debug)]
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -551,6 +553,7 @@ impl Operator {
| Operator::ShellCommand
| Operator::Lowercase
| Operator::Uppercase
| Operator::ReplaceWithRegister
| Operator::Object { .. }
| Operator::ChangeSurrounds { target: None }
| Operator::OppositeCase
Expand Down
1 change: 1 addition & 0 deletions docs/src/vim.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading