diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 7e2fbd82de..767de61e73 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -634,8 +634,7 @@ "ctrl-u": "editor::DeleteToBeginningOfLine", "ctrl-t": "vim::Indent", "ctrl-d": "vim::Outdent", - "ctrl-r \"": "editor::Paste", - "ctrl-r +": "editor::Paste" + "ctrl-r": ["vim::PushOperator", "Register"] } }, { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 62f7eaa878..da4cb30215 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6413,82 +6413,96 @@ impl Editor { cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections)); } - pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { + pub fn do_paste( + &mut self, + text: &String, + clipboard_selections: Option>, + handle_entire_lines: bool, + cx: &mut ViewContext, + ) { if self.read_only(cx) { return; } + let clipboard_text = Cow::Borrowed(text); + self.transact(cx, |this, cx| { - if let Some(item) = cx.read_from_clipboard() { - let clipboard_text = Cow::Borrowed(item.text()); - if let Some(mut clipboard_selections) = item.metadata::>() { - let old_selections = this.selections.all::(cx); - let all_selections_were_entire_line = - clipboard_selections.iter().all(|s| s.is_entire_line); - let first_selection_indent_column = - clipboard_selections.first().map(|s| s.first_line_indent); - if clipboard_selections.len() != old_selections.len() { - clipboard_selections.drain(..); - } - - this.buffer.update(cx, |buffer, cx| { - let snapshot = buffer.read(cx); - let mut start_offset = 0; - let mut edits = Vec::new(); - let mut original_indent_columns = Vec::new(); - let line_mode = this.selections.line_mode; - for (ix, selection) in old_selections.iter().enumerate() { - let to_insert; - let entire_line; - let original_indent_column; - if let Some(clipboard_selection) = clipboard_selections.get(ix) { - let end_offset = start_offset + clipboard_selection.len; - to_insert = &clipboard_text[start_offset..end_offset]; - entire_line = clipboard_selection.is_entire_line; - start_offset = end_offset + 1; - original_indent_column = - Some(clipboard_selection.first_line_indent); - } else { - to_insert = clipboard_text.as_str(); - entire_line = all_selections_were_entire_line; - original_indent_column = first_selection_indent_column - } - - // If the corresponding selection was empty when this slice of the - // clipboard text was written, then the entire line containing the - // selection was copied. If this selection is also currently empty, - // then paste the line before the current line of the buffer. - let range = if selection.is_empty() && !line_mode && entire_line { - let column = selection.start.to_point(&snapshot).column as usize; - let line_start = selection.start - column; - line_start..line_start - } else { - selection.range() - }; - - edits.push((range, to_insert)); - original_indent_columns.extend(original_indent_column); - } - drop(snapshot); - - buffer.edit( - edits, - Some(AutoindentMode::Block { - original_indent_columns, - }), - cx, - ); - }); - - let selections = this.selections.all::(cx); - this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); - } else { - this.insert(&clipboard_text, cx); + if let Some(mut clipboard_selections) = clipboard_selections { + let old_selections = this.selections.all::(cx); + let all_selections_were_entire_line = + clipboard_selections.iter().all(|s| s.is_entire_line); + let first_selection_indent_column = + clipboard_selections.first().map(|s| s.first_line_indent); + if clipboard_selections.len() != old_selections.len() { + clipboard_selections.drain(..); } + + this.buffer.update(cx, |buffer, cx| { + let snapshot = buffer.read(cx); + let mut start_offset = 0; + let mut edits = Vec::new(); + let mut original_indent_columns = Vec::new(); + for (ix, selection) in old_selections.iter().enumerate() { + let to_insert; + let entire_line; + let original_indent_column; + if let Some(clipboard_selection) = clipboard_selections.get(ix) { + let end_offset = start_offset + clipboard_selection.len; + to_insert = &clipboard_text[start_offset..end_offset]; + entire_line = clipboard_selection.is_entire_line; + start_offset = end_offset + 1; + original_indent_column = Some(clipboard_selection.first_line_indent); + } else { + to_insert = clipboard_text.as_str(); + entire_line = all_selections_were_entire_line; + original_indent_column = first_selection_indent_column + } + + // If the corresponding selection was empty when this slice of the + // clipboard text was written, then the entire line containing the + // selection was copied. If this selection is also currently empty, + // then paste the line before the current line of the buffer. + let range = if selection.is_empty() && handle_entire_lines && entire_line { + let column = selection.start.to_point(&snapshot).column as usize; + let line_start = selection.start - column; + line_start..line_start + } else { + selection.range() + }; + + edits.push((range, to_insert)); + original_indent_columns.extend(original_indent_column); + } + drop(snapshot); + + buffer.edit( + edits, + Some(AutoindentMode::Block { + original_indent_columns, + }), + cx, + ); + }); + + let selections = this.selections.all::(cx); + this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections)); + } else { + this.insert(&clipboard_text, cx); } }); } + pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { + if let Some(item) = cx.read_from_clipboard() { + self.do_paste( + item.text(), + item.metadata::>(), + true, + cx, + ) + }; + } + pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext) { if self.read_only(cx) { return; diff --git a/crates/vim/src/insert.rs b/crates/vim/src/insert.rs index cc268289f5..38b08a7d80 100644 --- a/crates/vim/src/insert.rs +++ b/crates/vim/src/insert.rs @@ -16,6 +16,11 @@ pub fn register(workspace: &mut Workspace, _: &mut ViewContext) { fn normal_before(_: &mut Workspace, action: &NormalBefore, cx: &mut ViewContext) { let should_repeat = Vim::update(cx, |vim, cx| { + if vim.state().active_operator().is_some() { + vim.update_state(|state| state.operator_stack.clear()); + vim.sync_vim_settings(cx); + return false; + } let count = vim.take_count(cx).unwrap_or(1); vim.stop_recording_immediately(action.boxed_clone()); if count <= 1 || vim.workspace_state.replaying { @@ -66,30 +71,24 @@ mod test { cx.set_shared_state("ˇhello\n").await; cx.simulate_shared_keystrokes("5 i - escape").await; - cx.run_until_parked(); cx.shared_state().await.assert_eq("----ˇ-hello\n"); cx.set_shared_state("ˇhello\n").await; cx.simulate_shared_keystrokes("5 a - escape").await; - cx.run_until_parked(); cx.shared_state().await.assert_eq("h----ˇ-ello\n"); cx.simulate_shared_keystrokes("4 shift-i - escape").await; - cx.run_until_parked(); cx.shared_state().await.assert_eq("---ˇ-h-----ello\n"); cx.simulate_shared_keystrokes("3 shift-a - escape").await; - cx.run_until_parked(); cx.shared_state().await.assert_eq("----h-----ello--ˇ-\n"); cx.set_shared_state("ˇhello\n").await; cx.simulate_shared_keystrokes("3 o o i escape").await; - cx.run_until_parked(); cx.shared_state().await.assert_eq("hello\noi\noi\noˇi\n"); cx.set_shared_state("ˇhello\n").await; cx.simulate_shared_keystrokes("3 shift-o o i escape").await; - cx.run_until_parked(); cx.shared_state().await.assert_eq("oi\noi\noˇi\nhello\n"); } @@ -99,28 +98,31 @@ mod test { cx.set_shared_state("ˇhello\n").await; cx.simulate_shared_keystrokes("3 i - escape").await; - cx.run_until_parked(); cx.shared_state().await.assert_eq("--ˇ-hello\n"); cx.simulate_shared_keystrokes(".").await; - cx.run_until_parked(); cx.shared_state().await.assert_eq("----ˇ--hello\n"); cx.simulate_shared_keystrokes("2 .").await; - cx.run_until_parked(); cx.shared_state().await.assert_eq("-----ˇ---hello\n"); cx.set_shared_state("ˇhello\n").await; cx.simulate_shared_keystrokes("2 o k k escape").await; - cx.run_until_parked(); cx.shared_state().await.assert_eq("hello\nkk\nkˇk\n"); cx.simulate_shared_keystrokes(".").await; - cx.run_until_parked(); cx.shared_state() .await .assert_eq("hello\nkk\nkk\nkk\nkˇk\n"); cx.simulate_shared_keystrokes("1 .").await; - cx.run_until_parked(); cx.shared_state() .await .assert_eq("hello\nkk\nkk\nkk\nkk\nkˇk\n"); } + + #[gpui::test] + async fn test_insert_ctrl_r(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.set_shared_state("heˇllo\n").await; + cx.simulate_shared_keystrokes("y y i ctrl-r \"").await; + cx.shared_state().await.assert_eq("hehello\nˇllo\n"); + } } diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 082e78136b..d94695c4aa 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -227,6 +227,7 @@ impl EditorState { Some(Operator::FindForward { .. }) | Some(Operator::FindBackward { .. }) | Some(Operator::Mark) + | Some(Operator::Register) | Some(Operator::Jump { .. }) ) } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 0ed33bce17..83eaa25f03 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -624,7 +624,7 @@ impl Vim { editor: Option<&mut Editor>, cx: &mut WindowContext, ) -> Option { - let Some(register) = register else { + let Some(register) = register.filter(|reg| *reg != '"') else { let setting = VimSettings::get_global(cx).use_system_clipboard; return match setting { UseSystemClipboard::Always => cx.read_from_clipboard().map(|item| item.into()), @@ -882,8 +882,24 @@ impl Vim { Some(Operator::Mark) => Vim::update(cx, |vim, cx| { normal::mark::create_mark(vim, text, false, cx) }), - Some(Operator::Register) => Vim::update(cx, |vim, cx| { - vim.select_register(text, cx); + Some(Operator::Register) => Vim::update(cx, |vim, cx| match vim.state().mode { + Mode::Insert => { + vim.update_active_editor(cx, |vim, editor, cx| { + if let Some(register) = + vim.read_register(text.chars().next(), Some(editor), cx) + { + editor.do_paste( + ®ister.text.to_string(), + register.clipboard_selections.clone(), + false, + cx, + ) + } + }); + } + _ => { + vim.select_register(text, cx); + } }), Some(Operator::Jump { line }) => normal::mark::jump(text, line, cx), _ => match Vim::read(cx).state().mode { diff --git a/crates/vim/test_data/test_insert_ctrl_r.json b/crates/vim/test_data/test_insert_ctrl_r.json new file mode 100644 index 0000000000..c61027157e --- /dev/null +++ b/crates/vim/test_data/test_insert_ctrl_r.json @@ -0,0 +1,7 @@ +{"Put":{"state":"heˇllo\n"}} +{"Key":"y"} +{"Key":"y"} +{"Key":"i"} +{"Key":"ctrl-r"} +{"Key":"\""} +{"Get":{"state":"hehello\nˇllo\n","mode":"Insert"}}