From 001f17c01129635a0664fee4bd424ccb9cd3f3d5 Mon Sep 17 00:00:00 2001 From: Paul Eguisier Date: Wed, 12 Jun 2024 18:40:27 +0200 Subject: [PATCH] vim: Implement named registers (#12895) Release Notes: - vim: Add support for register selection `"a`-`"z`, `"0`-`"9`, `"-`. `"_` and `"%` ([#11511](https://github.com/zed-industries/zed/issues/11511)) --------- Co-authored-by: Conrad Irwin --- assets/keymaps/vim.json | 2 + crates/editor/src/editor.rs | 2 +- crates/gpui/src/keymap/context.rs | 2 +- crates/vim/src/mode_indicator.rs | 14 +- crates/vim/src/normal/paste.rs | 178 +++++++++++++++--- crates/vim/src/state.rs | 8 +- .../src/test/neovim_backed_test_context.rs | 27 ++- crates/vim/src/utils.rs | 24 +-- crates/vim/src/vim.rs | 156 ++++++++++++++- .../vim/test_data/test_named_registers.json | 26 +++ .../test_data/test_numbered_registers.json | 45 +++++ .../vim/test_data/test_special_registers.json | 30 +++ docs/src/vim.md | 6 +- 13 files changed, 454 insertions(+), 66 deletions(-) create mode 100644 crates/vim/test_data/test_named_registers.json create mode 100644 crates/vim/test_data/test_numbered_registers.json create mode 100644 crates/vim/test_data/test_special_registers.json diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 08da79078a..c9df3be659 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -385,6 +385,7 @@ "g u": ["vim::PushOperator", "Lowercase"], "g shift-u": ["vim::PushOperator", "Uppercase"], "g ~": ["vim::PushOperator", "OppositeCase"], + "\"": ["vim::PushOperator", "Register"], "ctrl-pagedown": "pane::ActivateNextItem", "ctrl-pageup": "pane::ActivatePrevItem", // tree-sitter related commands @@ -399,6 +400,7 @@ { "context": "Editor && vim_mode == visual && vim_operator == none && !VimWaiting", "bindings": { + "\"": ["vim::PushOperator", "Register"], // tree-sitter related commands "[ x": "editor::SelectLargerSyntaxNode", "] x": "editor::SelectSmallerSyntaxNode" diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9b129ad9f5..a1aff7cbe8 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1502,7 +1502,7 @@ struct ActiveDiagnosticGroup { is_valid: bool, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct ClipboardSelection { pub len: usize, pub is_entire_line: bool, diff --git a/crates/gpui/src/keymap/context.rs b/crates/gpui/src/keymap/context.rs index dbe0e10b8d..2990bff196 100644 --- a/crates/gpui/src/keymap/context.rs +++ b/crates/gpui/src/keymap/context.rs @@ -356,7 +356,7 @@ fn is_identifier_char(c: char) -> bool { } fn is_vim_operator_char(c: char) -> bool { - c == '>' || c == '<' || c == '~' + c == '>' || c == '<' || c == '~' || c == '"' } fn skip_whitespace(source: &str) -> &str { diff --git a/crates/vim/src/mode_indicator.rs b/crates/vim/src/mode_indicator.rs index 9c9f6f3207..784ac03f33 100644 --- a/crates/vim/src/mode_indicator.rs +++ b/crates/vim/src/mode_indicator.rs @@ -39,9 +39,17 @@ impl ModeIndicator { fn current_operators_description(&self, vim: &Vim) -> String { vim.state() - .operator_stack - .iter() - .map(|item| item.id()) + .pre_count + .map(|count| format!("{}", count)) + .into_iter() + .chain(vim.state().selected_register.map(|reg| format!("\"{reg}"))) + .chain( + vim.state() + .operator_stack + .iter() + .map(|item| item.id().to_string()), + ) + .chain(vim.state().post_count.map(|count| format!("{}", count))) .collect::>() .join("") } diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index fc5569e45e..fa5360b96a 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -10,7 +10,11 @@ use serde::Deserialize; use settings::Settings; use workspace::Workspace; -use crate::{state::Mode, utils::copy_selections_content, UseSystemClipboard, Vim, VimSettings}; +use crate::{ + state::Mode, + utils::{copy_selections_content, SYSTEM_CLIPBOARD}, + UseSystemClipboard, Vim, VimSettings, +}; #[derive(Clone, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] @@ -29,7 +33,7 @@ pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext fn system_clipboard_is_newer(vim: &Vim, cx: &mut AppContext) -> bool { cx.read_from_clipboard().is_some_and(|item| { - if let Some(last_state) = vim.workspace_state.registers.get(".system.") { + if let Some(last_state) = vim.workspace_state.registers.get(&SYSTEM_CLIPBOARD) { last_state != item.text() } else { true @@ -46,33 +50,36 @@ fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext) { editor.transact(cx, |editor, cx| { editor.set_clip_at_line_ends(false, cx); - let (clipboard_text, clipboard_selections): (String, Option<_>) = - if VimSettings::get_global(cx).use_system_clipboard == UseSystemClipboard::Never - || VimSettings::get_global(cx).use_system_clipboard - == UseSystemClipboard::OnYank - && !system_clipboard_is_newer(vim, cx) - { - ( - vim.workspace_state - .registers - .get("\"") - .cloned() - .unwrap_or_else(|| "".to_string()), - None, - ) + let (clipboard_text, clipboard_selections): (String, Option<_>) = if let Some( + register, + ) = + vim.update_state(|state| state.selected_register.take()) + { + ( + vim.read_register(register, Some(editor), cx) + .unwrap_or_default(), + None, + ) + } else if VimSettings::get_global(cx).use_system_clipboard + == UseSystemClipboard::Never + || VimSettings::get_global(cx).use_system_clipboard + == UseSystemClipboard::OnYank + && !system_clipboard_is_newer(vim, cx) + { + (vim.read_register('"', None, cx).unwrap_or_default(), None) + } else { + if let Some(item) = cx.read_from_clipboard() { + let clipboard_selections = item + .metadata::>() + .filter(|clipboard_selections| { + clipboard_selections.len() > 1 + && vim.state().mode != Mode::VisualLine + }); + (item.text().clone(), clipboard_selections) } else { - if let Some(item) = cx.read_from_clipboard() { - let clipboard_selections = item - .metadata::>() - .filter(|clipboard_selections| { - clipboard_selections.len() > 1 - && vim.state().mode != Mode::VisualLine - }); - (item.text().clone(), clipboard_selections) - } else { - ("".into(), None) - } - }; + ("".into(), None) + } + }; if clipboard_text.is_empty() { return; @@ -606,4 +613,119 @@ mod test { three "}); } + + #[gpui::test] + async fn test_numbered_registers(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |s| { + s.use_system_clipboard = Some(UseSystemClipboard::Never) + }); + }); + + cx.set_shared_state(indoc! {" + The quick brown + fox jˇumps over + the lazy dog"}) + .await; + cx.simulate_shared_keystrokes("y y \" 0 p").await; + cx.shared_register('0').await.assert_eq("fox jumps over\n"); + cx.shared_register('"').await.assert_eq("fox jumps over\n"); + + cx.shared_state().await.assert_eq(indoc! {" + The quick brown + fox jumps over + ˇfox jumps over + the lazy dog"}); + cx.simulate_shared_keystrokes("k k d d").await; + cx.shared_register('0').await.assert_eq("fox jumps over\n"); + cx.shared_register('1').await.assert_eq("The quick brown\n"); + cx.shared_register('"').await.assert_eq("The quick brown\n"); + + cx.simulate_shared_keystrokes("d d shift-g d d").await; + cx.shared_register('0').await.assert_eq("fox jumps over\n"); + cx.shared_register('3').await.assert_eq("The quick brown\n"); + cx.shared_register('2').await.assert_eq("fox jumps over\n"); + cx.shared_register('1').await.assert_eq("the lazy dog\n"); + + cx.shared_state().await.assert_eq(indoc! {" + ˇfox jumps over"}); + + cx.simulate_shared_keystrokes("d d \" 3 p p \" 1 p").await; + cx.set_shared_state(indoc! {" + The quick brown + fox jumps over + ˇthe lazy dog"}) + .await; + } + + #[gpui::test] + async fn test_named_registers(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |s| { + s.use_system_clipboard = Some(UseSystemClipboard::Never) + }); + }); + + cx.set_shared_state(indoc! {" + The quick brown + fox jˇumps over + the lazy dog"}) + .await; + cx.simulate_shared_keystrokes("\" a d a w").await; + cx.shared_register('a').await.assert_eq("jumps "); + cx.simulate_shared_keystrokes("\" shift-a d i w").await; + cx.shared_register('a').await.assert_eq("jumps over"); + cx.simulate_shared_keystrokes("\" a p").await; + cx.shared_state().await.assert_eq(indoc! {" + The quick brown + fox jumps oveˇr + the lazy dog"}); + cx.simulate_shared_keystrokes("\" a d a w").await; + cx.shared_register('a').await.assert_eq(" over"); + } + + #[gpui::test] + async fn test_special_registers(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |s| { + s.use_system_clipboard = Some(UseSystemClipboard::Never) + }); + }); + + cx.set_shared_state(indoc! {" + The quick brown + fox jˇumps over + the lazy dog"}) + .await; + cx.simulate_shared_keystrokes("d i w").await; + cx.shared_register('-').await.assert_eq("jumps"); + cx.simulate_shared_keystrokes("\" _ d d").await; + cx.shared_register('_').await.assert_eq(""); + + cx.shared_state().await.assert_eq(indoc! {" + The quick brown + the ˇlazy dog"}); + cx.simulate_shared_keystrokes("\" \" d ^").await; + cx.shared_register('0').await.assert_eq("the "); + cx.shared_register('"').await.assert_eq("the "); + + cx.simulate_shared_keystrokes("^ \" + d $").await; + cx.shared_clipboard().await.assert_eq("lazy dog"); + cx.shared_register('"').await.assert_eq("lazy dog"); + + // not testing nvim as it doesn't have a filename + cx.simulate_keystrokes("\" % p"); + cx.assert_state( + indoc! {" + The quick brown + dir/file.rˇs"}, + Mode::Normal, + ); + } } diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 122cc068e0..6d18f94b2e 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -63,10 +63,10 @@ pub enum Operator { Jump { line: bool }, Indent, Outdent, - Lowercase, Uppercase, OppositeCase, + Register, } #[derive(Default, Clone)] @@ -89,6 +89,8 @@ pub struct EditorState { pub current_tx: Option, pub current_anchor: Option>, pub undo_modes: HashMap, + + pub selected_register: Option, } #[derive(Default, Clone, Debug)] @@ -123,7 +125,7 @@ pub struct WorkspaceState { pub recorded_actions: Vec, pub recorded_selection: RecordedSelection, - pub registers: HashMap, + pub registers: HashMap, } #[derive(Debug)] @@ -277,6 +279,7 @@ impl Operator { Operator::Uppercase => "gU", Operator::Lowercase => "gu", Operator::OppositeCase => "g~", + Operator::Register => "\"", } } @@ -287,6 +290,7 @@ impl Operator { | Operator::Mark | Operator::Jump { .. } | Operator::FindBackward { .. } + | Operator::Register | Operator::Replace | Operator::AddSurrounds { target: Some(_) } | Operator::ChangeSurrounds { .. } diff --git a/crates/vim/src/test/neovim_backed_test_context.rs b/crates/vim/src/test/neovim_backed_test_context.rs index 7114516471..f7e4e28702 100644 --- a/crates/vim/src/test/neovim_backed_test_context.rs +++ b/crates/vim/src/test/neovim_backed_test_context.rs @@ -10,7 +10,7 @@ use language::language_settings::{AllLanguageSettings, SoftWrap}; use util::test::marked_text_offsets; use super::{neovim_connection::NeovimConnection, VimTestContext}; -use crate::state::Mode; +use crate::{state::Mode, Vim}; pub struct NeovimBackedTestContext { cx: VimTestContext, @@ -94,6 +94,7 @@ impl SharedState { } pub struct SharedClipboard { + register: char, neovim: String, editor: String, state: SharedState, @@ -120,15 +121,17 @@ impl SharedClipboard { {} # currently expected: {} - # neovim clipboard: + # neovim register \"{}: {} - # zed clipboard: + # zed register \"{}: {}"}, message, self.state.initial, self.state.recent_keystrokes, expected, + self.register, self.neovim, + self.register, self.editor ) } @@ -241,12 +244,30 @@ impl NeovimBackedTestContext { #[must_use] pub async fn shared_clipboard(&mut self) -> SharedClipboard { SharedClipboard { + register: '"', state: self.shared_state().await, neovim: self.neovim.read_register('"').await, editor: self.read_from_clipboard().unwrap().text().clone(), } } + #[must_use] + pub async fn shared_register(&mut self, register: char) -> SharedClipboard { + SharedClipboard { + register: register, + state: self.shared_state().await, + neovim: self.neovim.read_register(register).await, + editor: self.update(|cx| { + Vim::read(cx) + .workspace_state + .registers + .get(®ister) + .cloned() + .unwrap_or_default() + }), + } + } + #[must_use] pub async fn shared_state(&mut self) -> SharedState { let (mode, marked_text) = self.neovim.state().await; diff --git a/crates/vim/src/utils.rs b/crates/vim/src/utils.rs index e8fb1d2e24..b9bb30fe86 100644 --- a/crates/vim/src/utils.rs +++ b/crates/vim/src/utils.rs @@ -1,12 +1,13 @@ use std::time::Duration; use editor::{ClipboardSelection, Editor}; -use gpui::{ClipboardItem, ViewContext}; +use gpui::ViewContext; use language::{CharKind, Point}; use multi_buffer::MultiBufferRow; -use settings::Settings; -use crate::{state::Mode, UseSystemClipboard, Vim, VimSettings}; +use crate::{state::Mode, Vim}; + +pub const SYSTEM_CLIPBOARD: char = '\0'; pub struct HighlightOnYank; @@ -102,21 +103,8 @@ fn copy_selections_content_internal( } } - let setting = VimSettings::get_global(cx).use_system_clipboard; - if setting == UseSystemClipboard::Always || setting == UseSystemClipboard::OnYank && is_yank { - cx.write_to_clipboard(ClipboardItem::new(text.clone()).with_metadata(clipboard_selections)); - vim.workspace_state - .registers - .insert(".system.".to_string(), text.clone()); - } else { - vim.workspace_state.registers.insert( - ".system.".to_string(), - cx.read_from_clipboard() - .map(|item| item.text().clone()) - .unwrap_or_default(), - ); - } - vim.workspace_state.registers.insert("\"".to_string(), text); + vim.write_registers(is_yank, linewise, text, clipboard_selections, cx); + if !is_yank || vim.state().mode == Mode::Visual { return; } diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index f0980d925a..78acb66e95 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -23,11 +23,11 @@ use collections::HashMap; use command_palette_hooks::{CommandPaletteFilter, CommandPaletteInterceptor}; use editor::{ movement::{self, FindRange}, - Anchor, Bias, Editor, EditorEvent, EditorMode, ToPoint, + Anchor, Bias, ClipboardSelection, Editor, EditorEvent, EditorMode, ToPoint, }; use gpui::{ - actions, impl_actions, Action, AppContext, EntityId, FocusableView, Global, KeystrokeEvent, - Subscription, UpdateGlobal, View, ViewContext, WeakView, WindowContext, + actions, impl_actions, Action, AppContext, ClipboardItem, EntityId, FocusableView, Global, + KeystrokeEvent, Subscription, UpdateGlobal, View, ViewContext, WeakView, WindowContext, }; use language::{CursorShape, Point, SelectionGoal, TransactionId}; pub use mode_indicator::ModeIndicator; @@ -45,6 +45,7 @@ use state::{EditorState, Mode, Operator, RecordedSelection, WorkspaceState}; use std::{ops::Range, sync::Arc}; use surrounds::{add_surrounds, change_surrounds, delete_surrounds}; use ui::BorrowAppContext; +use utils::SYSTEM_CLIPBOARD; use visual::{visual_block_motion, visual_replace}; use workspace::{self, Workspace}; @@ -70,6 +71,9 @@ pub struct PushOperator(pub Operator); #[derive(Clone, Deserialize, PartialEq)] struct Number(usize); +#[derive(Clone, Deserialize, PartialEq)] +struct SelectRegister(String); + actions!( vim, [ @@ -86,7 +90,7 @@ actions!( // in the workspace namespace so it's not filtered out when vim is disabled. actions!(workspace, [ToggleVimMode]); -impl_actions!(vim, [SwitchMode, PushOperator, Number]); +impl_actions!(vim, [SwitchMode, PushOperator, Number, SelectRegister]); /// Initializes the `vim` crate. pub fn init(cx: &mut AppContext) { @@ -129,7 +133,6 @@ fn register(workspace: &mut Workspace, cx: &mut ViewContext) { workspace.register_action(|_: &mut Workspace, n: &Number, cx: _| { Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx)); }); - workspace.register_action(|_: &mut Workspace, _: &Tab, cx| { Vim::active_editor_input_ignored(" ".into(), cx) }); @@ -202,7 +205,8 @@ fn observe_keystrokes(keystroke_event: &KeystrokeEvent, cx: &mut WindowContext) | Operator::ChangeSurrounds { .. } | Operator::DeleteSurrounds | Operator::Mark - | Operator::Jump { .. }, + | Operator::Jump { .. } + | Operator::Register, ) => {} Some(_) => { vim.clear_operator(cx); @@ -531,6 +535,138 @@ impl Vim { count } + fn select_register(&mut self, register: Arc, cx: &mut WindowContext) { + self.update_state(|state| { + if register.chars().count() == 1 { + state + .selected_register + .replace(register.chars().next().unwrap()); + } + state.operator_stack.clear(); + }); + self.sync_vim_settings(cx); + } + + fn write_registers( + &mut self, + is_yank: bool, + linewise: bool, + text: String, + clipboard_selections: Vec, + cx: &mut ViewContext, + ) { + self.workspace_state.registers.insert('"', text.clone()); + if let Some(register) = self.update_state(|vim| vim.selected_register.take()) { + let lower = register.to_lowercase().next().unwrap_or(register); + if lower != register { + let current = self.workspace_state.registers.entry(lower).or_default(); + *current += &text; + } else { + match lower { + '_' | ':' | '.' | '%' | '#' | '=' | '/' => {} + '+' => { + cx.write_to_clipboard( + ClipboardItem::new(text.clone()).with_metadata(clipboard_selections), + ); + } + '*' => { + #[cfg(target_os = "linux")] + cx.write_to_primary( + ClipboardItem::new(text.clone()).with_metadata(clipboard_selections), + ); + #[cfg(not(target_os = "linux"))] + cx.write_to_clipboard( + ClipboardItem::new(text.clone()).with_metadata(clipboard_selections), + ); + } + '"' => { + self.workspace_state.registers.insert('0', text.clone()); + self.workspace_state.registers.insert('"', text); + } + _ => { + self.workspace_state.registers.insert(lower, text); + } + } + } + } else { + let setting = VimSettings::get_global(cx).use_system_clipboard; + if setting == UseSystemClipboard::Always + || setting == UseSystemClipboard::OnYank && is_yank + { + cx.write_to_clipboard( + ClipboardItem::new(text.clone()).with_metadata(clipboard_selections.clone()), + ); + self.workspace_state + .registers + .insert(SYSTEM_CLIPBOARD, text.clone()); + } else { + self.workspace_state.registers.insert( + SYSTEM_CLIPBOARD, + cx.read_from_clipboard() + .map(|item| item.text().clone()) + .unwrap_or_default(), + ); + } + + if is_yank { + self.workspace_state.registers.insert('0', text); + } else { + if !text.contains('\n') { + self.workspace_state.registers.insert('-', text.clone()); + } + if linewise || text.contains('\n') { + let mut content = text; + for i in '1'..'8' { + if let Some(moved) = self.workspace_state.registers.insert(i, content) { + content = moved; + } else { + break; + } + } + } + } + } + } + + fn read_register( + &mut self, + register: char, + editor: Option<&mut Editor>, + cx: &mut WindowContext, + ) -> Option { + let lower = register.to_lowercase().next().unwrap_or(register); + match lower { + '_' | ':' | '.' | '#' | '=' | '/' => None, + '+' => cx.read_from_clipboard().map(|item| item.text().clone()), + '*' => { + #[cfg(target_os = "linux")] + { + cx.read_from_primary().map(|item| item.text().clone()) + } + #[cfg(not(target_os = "linux"))] + { + cx.read_from_clipboard().map(|item| item.text().clone()) + } + } + '%' => editor.and_then(|editor| { + let selection = editor.selections.newest::(cx); + if let Some((_, buffer, _)) = editor + .buffer() + .read(cx) + .excerpt_containing(selection.head(), cx) + { + buffer + .read(cx) + .file() + .map(|file| file.path().to_string_lossy().to_string()) + } else { + None + } + }), + _ => self.workspace_state.registers.get(&lower).cloned(), + } + } + fn push_operator(&mut self, operator: Operator, cx: &mut WindowContext) { if matches!( operator, @@ -573,7 +709,10 @@ impl Vim { fn clear_operator(&mut self, cx: &mut WindowContext) { self.take_count(cx); - self.update_state(|state| state.operator_stack.clear()); + self.update_state(|state| { + state.selected_register.take(); + state.operator_stack.clear() + }); self.sync_vim_settings(cx); } @@ -741,6 +880,9 @@ 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::Jump { line }) => normal::mark::jump(text, line, cx), _ => match Vim::read(cx).state().mode { Mode::Replace => multi_replace(text, cx), diff --git a/crates/vim/test_data/test_named_registers.json b/crates/vim/test_data/test_named_registers.json new file mode 100644 index 0000000000..886a19ad9e --- /dev/null +++ b/crates/vim/test_data/test_named_registers.json @@ -0,0 +1,26 @@ +{"Put":{"state":"The quick brown\nfox jˇumps over\nthe lazy dog"}} +{"Key":"\""} +{"Key":"a"} +{"Key":"d"} +{"Key":"a"} +{"Key":"w"} +{"Get":{"state":"The quick brown\nfox ˇover\nthe lazy dog","mode":"Normal"}} +{"ReadRegister":{"name":"a","value":"jumps "}} +{"Key":"\""} +{"Key":"shift-a"} +{"Key":"d"} +{"Key":"i"} +{"Key":"w"} +{"Get":{"state":"The quick brown\nfoxˇ \nthe lazy dog","mode":"Normal"}} +{"ReadRegister":{"name":"a","value":"jumps over"}} +{"Key":"\""} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown\nfox jumps oveˇr\nthe lazy dog","mode":"Normal"}} +{"Key":"\""} +{"Key":"a"} +{"Key":"d"} +{"Key":"a"} +{"Key":"w"} +{"Get":{"state":"The quick brown\nfox jumpˇs\nthe lazy dog","mode":"Normal"}} +{"ReadRegister":{"name":"a","value":" over"}} diff --git a/crates/vim/test_data/test_numbered_registers.json b/crates/vim/test_data/test_numbered_registers.json new file mode 100644 index 0000000000..191a58e01f --- /dev/null +++ b/crates/vim/test_data/test_numbered_registers.json @@ -0,0 +1,45 @@ +{"Put":{"state":"The quick brown\nfox jˇumps over\nthe lazy dog"}} +{"Key":"y"} +{"Key":"y"} +{"Key":"\""} +{"Key":"0"} +{"Key":"p"} +{"Get":{"state":"The quick brown\nfox jumps over\nˇfox jumps over\nthe lazy dog","mode":"Normal"}} +{"ReadRegister":{"name":"0","value":"fox jumps over\n"}} +{"Get":{"state":"The quick brown\nfox jumps over\nˇfox jumps over\nthe lazy dog","mode":"Normal"}} +{"ReadRegister":{"name":"\"","value":"fox jumps over\n"}} +{"Get":{"state":"The quick brown\nfox jumps over\nˇfox jumps over\nthe lazy dog","mode":"Normal"}} +{"Key":"k"} +{"Key":"k"} +{"Key":"d"} +{"Key":"d"} +{"Get":{"state":"ˇfox jumps over\nfox jumps over\nthe lazy dog","mode":"Normal"}} +{"ReadRegister":{"name":"0","value":"fox jumps over\n"}} +{"Get":{"state":"ˇfox jumps over\nfox jumps over\nthe lazy dog","mode":"Normal"}} +{"ReadRegister":{"name":"1","value":"The quick brown\n"}} +{"Get":{"state":"ˇfox jumps over\nfox jumps over\nthe lazy dog","mode":"Normal"}} +{"ReadRegister":{"name":"\"","value":"The quick brown\n"}} +{"Key":"d"} +{"Key":"d"} +{"Key":"shift-g"} +{"Key":"d"} +{"Key":"d"} +{"Get":{"state":"ˇfox jumps over","mode":"Normal"}} +{"ReadRegister":{"name":"0","value":"fox jumps over\n"}} +{"Get":{"state":"ˇfox jumps over","mode":"Normal"}} +{"ReadRegister":{"name":"3","value":"The quick brown\n"}} +{"Get":{"state":"ˇfox jumps over","mode":"Normal"}} +{"ReadRegister":{"name":"2","value":"fox jumps over\n"}} +{"Get":{"state":"ˇfox jumps over","mode":"Normal"}} +{"ReadRegister":{"name":"1","value":"the lazy dog\n"}} +{"Get":{"state":"ˇfox jumps over","mode":"Normal"}} +{"Key":"d"} +{"Key":"d"} +{"Key":"\""} +{"Key":"3"} +{"Key":"p"} +{"Key":"p"} +{"Key":"\""} +{"Key":"1"} +{"Key":"p"} +{"Put":{"state":"The quick brown\nfox jumps over\nˇthe lazy dog"}} diff --git a/crates/vim/test_data/test_special_registers.json b/crates/vim/test_data/test_special_registers.json new file mode 100644 index 0000000000..29f27cc688 --- /dev/null +++ b/crates/vim/test_data/test_special_registers.json @@ -0,0 +1,30 @@ +{"Put":{"state":"The quick brown\nfox jˇumps over\nthe lazy dog"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"w"} +{"Get":{"state":"The quick brown\nfox ˇ over\nthe lazy dog","mode":"Normal"}} +{"ReadRegister":{"name":"-","value":"jumps"}} +{"Key":"\""} +{"Key":"_"} +{"Key":"d"} +{"Key":"d"} +{"Get":{"state":"The quick brown\nthe ˇlazy dog","mode":"Normal"}} +{"ReadRegister":{"name":"_","value":""}} +{"Get":{"state":"The quick brown\nthe ˇlazy dog","mode":"Normal"}} +{"Key":"\""} +{"Key":"\""} +{"Key":"d"} +{"Key":"^"} +{"Get":{"state":"The quick brown\nˇlazy dog","mode":"Normal"}} +{"ReadRegister":{"name":"0","value":"the "}} +{"Get":{"state":"The quick brown\nˇlazy dog","mode":"Normal"}} +{"ReadRegister":{"name":"\"","value":"the "}} +{"Key":"^"} +{"Key":"\""} +{"Key":"+"} +{"Key":"d"} +{"Key":"$"} +{"Get":{"state":"The quick brown\nˇ","mode":"Normal"}} +{"ReadRegister":{"name":"\"","value":"lazy dog"}} +{"Get":{"state":"The quick brown\nˇ","mode":"Normal"}} +{"ReadRegister":{"name":"\"","value":"lazy dog"}} diff --git a/docs/src/vim.md b/docs/src/vim.md index dba4414c6c..53ee22ee36 100644 --- a/docs/src/vim.md +++ b/docs/src/vim.md @@ -215,9 +215,9 @@ Some vim settings are available to modify the default vim behavior: ```json { "vim": { - // "always": use system clipboard - // "never": don't use system clipboard - // "on_yank": use system clipboard for yank operations + // "always": use system clipboard when no register is specified + // "never": don't use system clipboard unless "+ or "* is specified + // "on_yank": use system clipboard for yank operations when no register is specified "use_system_clipboard": "always", // Lets `f` and `t` motions extend across multiple lines "use_multiline_find": true