vim: Add S to substitute line

For zed-industries/community#1897
This commit is contained in:
Conrad Irwin 2023-09-01 12:23:45 -06:00
parent 0e41c6c5b3
commit af12977d17
5 changed files with 128 additions and 13 deletions

View File

@ -371,6 +371,7 @@
"Replace"
],
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
"> >": "editor::Indent",
"< <": "editor::Outdent",
"ctrl-pagedown": "pane::ActivateNextItem",
@ -446,6 +447,7 @@
}
],
"s": "vim::Substitute",
"shift-s": "vim::SubstituteLine",
"c": "vim::Substitute",
"~": "vim::ChangeCase",
"shift-i": [

View File

@ -42,8 +42,8 @@
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
"state": {
"branch": null,
"revision": "ce20dc083ee485524b802669890291c0d8090170",
"version": "1.22.1"
"revision": "0af9125c4eae12a4973fb66574c53a54962a9e1e",
"version": "1.21.0"
}
}
]

View File

@ -27,7 +27,6 @@ use self::{
case::change_case,
change::{change_motion, change_object},
delete::{delete_motion, delete_object},
substitute::substitute,
yank::{yank_motion, yank_object},
};
@ -44,7 +43,6 @@ actions!(
ChangeToEndOfLine,
DeleteToEndOfLine,
Yank,
Substitute,
ChangeCase,
]
);
@ -56,13 +54,8 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(insert_line_above);
cx.add_action(insert_line_below);
cx.add_action(change_case);
substitute::init(cx);
search::init(cx);
cx.add_action(|_: &mut Workspace, _: &Substitute, cx| {
Vim::update(cx, |vim, cx| {
let times = vim.pop_number_operator(cx);
substitute(vim, times, cx);
})
});
cx.add_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
Vim::update(cx, |vim, cx| {
let times = vim.pop_number_operator(cx);

View File

@ -1,10 +1,32 @@
use gpui::WindowContext;
use editor::movement;
use gpui::{actions, AppContext, WindowContext};
use language::Point;
use workspace::Workspace;
use crate::{motion::Motion, utils::copy_selections_content, Mode, Vim};
pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
let line_mode = vim.state().mode == Mode::VisualLine;
actions!(vim, [Substitute, SubstituteLine]);
pub(crate) fn init(cx: &mut AppContext) {
cx.add_action(|_: &mut Workspace, _: &Substitute, cx| {
Vim::update(cx, |vim, cx| {
let times = vim.pop_number_operator(cx);
substitute(vim, times, vim.state().mode == Mode::VisualLine, cx);
})
});
cx.add_action(|_: &mut Workspace, _: &SubstituteLine, cx| {
Vim::update(cx, |vim, cx| {
if matches!(vim.state().mode, Mode::VisualBlock | Mode::Visual) {
vim.switch_mode(Mode::VisualLine, false, cx)
}
let count = vim.pop_number_operator(cx);
substitute(vim, count, true, cx)
})
});
}
pub fn substitute(vim: &mut Vim, count: Option<usize>, line_mode: bool, cx: &mut WindowContext) {
vim.update_active_editor(cx, |editor, cx| {
editor.set_clip_at_line_ends(false, cx);
editor.transact(cx, |editor, cx| {
@ -14,6 +36,11 @@ pub fn substitute(vim: &mut Vim, count: Option<usize>, cx: &mut WindowContext) {
Motion::Right.expand_selection(map, selection, count, true);
}
if line_mode {
// in Visual mode when the selection contains the newline at the end
// of the line, we should exclude it.
if !selection.is_empty() && selection.end.column() == 0 {
selection.end = movement::left(map, selection.end);
}
Motion::CurrentLine.expand_selection(map, selection, None, false);
if let Some((point, _)) = (Motion::FirstNonWhitespace {
display_lines: false,
@ -166,4 +193,68 @@ mod test {
the laˇzy dog"})
.await;
}
#[gpui::test]
async fn test_substitute_line(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
let initial_state = indoc! {"
The quick brown
fox juˇmps over
the lazy dog
"};
// normal mode
cx.set_shared_state(initial_state).await;
cx.simulate_shared_keystrokes(["shift-s", "o"]).await;
cx.assert_shared_state(indoc! {"
The quick brown
the lazy dog
"})
.await;
// visual mode
cx.set_shared_state(initial_state).await;
cx.simulate_shared_keystrokes(["v", "k", "shift-s", "o"])
.await;
cx.assert_shared_state(indoc! {"
the lazy dog
"})
.await;
// visual block mode
cx.set_shared_state(initial_state).await;
cx.simulate_shared_keystrokes(["ctrl-v", "j", "shift-s", "o"])
.await;
cx.assert_shared_state(indoc! {"
The quick brown
"})
.await;
// visual mode including newline
cx.set_shared_state(initial_state).await;
cx.simulate_shared_keystrokes(["v", "$", "shift-s", "o"])
.await;
cx.assert_shared_state(indoc! {"
The quick brown
the lazy dog
"})
.await;
// indentation
cx.set_neovim_option("shiftwidth=4").await;
cx.set_shared_state(initial_state).await;
cx.simulate_shared_keystrokes([">", ">", "shift-s", "o"])
.await;
cx.assert_shared_state(indoc! {"
The quick brown
the lazy dog
"})
.await;
}
}

View File

@ -0,0 +1,29 @@
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
{"Key":"shift-s"}
{"Key":"o"}
{"Get":{"state":"The quick brown\noˇ\nthe lazy dog\n","mode":"Insert"}}
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
{"Key":"v"}
{"Key":"k"}
{"Key":"shift-s"}
{"Key":"o"}
{"Get":{"state":"oˇ\nthe lazy dog\n","mode":"Insert"}}
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
{"Key":"ctrl-v"}
{"Key":"j"}
{"Key":"shift-s"}
{"Key":"o"}
{"Get":{"state":"The quick brown\noˇ\n","mode":"Insert"}}
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
{"Key":"v"}
{"Key":"$"}
{"Key":"shift-s"}
{"Key":"o"}
{"Get":{"state":"The quick brown\noˇ\nthe lazy dog\n","mode":"Insert"}}
{"SetOption":{"value":"shiftwidth=4"}}
{"Put":{"state":"The quick brown\nfox juˇmps over\nthe lazy dog\n"}}
{"Key":">"}
{"Key":">"}
{"Key":"shift-s"}
{"Key":"o"}
{"Get":{"state":"The quick brown\n oˇ\nthe lazy dog\n","mode":"Insert"}}