mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 18:41:56 +03:00
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 <conrad.irwin@gmail.com>
This commit is contained in:
parent
3c3dad6830
commit
001f17c011
@ -385,6 +385,7 @@
|
|||||||
"g u": ["vim::PushOperator", "Lowercase"],
|
"g u": ["vim::PushOperator", "Lowercase"],
|
||||||
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
"g shift-u": ["vim::PushOperator", "Uppercase"],
|
||||||
"g ~": ["vim::PushOperator", "OppositeCase"],
|
"g ~": ["vim::PushOperator", "OppositeCase"],
|
||||||
|
"\"": ["vim::PushOperator", "Register"],
|
||||||
"ctrl-pagedown": "pane::ActivateNextItem",
|
"ctrl-pagedown": "pane::ActivateNextItem",
|
||||||
"ctrl-pageup": "pane::ActivatePrevItem",
|
"ctrl-pageup": "pane::ActivatePrevItem",
|
||||||
// tree-sitter related commands
|
// tree-sitter related commands
|
||||||
@ -399,6 +400,7 @@
|
|||||||
{
|
{
|
||||||
"context": "Editor && vim_mode == visual && vim_operator == none && !VimWaiting",
|
"context": "Editor && vim_mode == visual && vim_operator == none && !VimWaiting",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
"\"": ["vim::PushOperator", "Register"],
|
||||||
// tree-sitter related commands
|
// tree-sitter related commands
|
||||||
"[ x": "editor::SelectLargerSyntaxNode",
|
"[ x": "editor::SelectLargerSyntaxNode",
|
||||||
"] x": "editor::SelectSmallerSyntaxNode"
|
"] x": "editor::SelectSmallerSyntaxNode"
|
||||||
|
@ -1502,7 +1502,7 @@ struct ActiveDiagnosticGroup {
|
|||||||
is_valid: bool,
|
is_valid: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct ClipboardSelection {
|
pub struct ClipboardSelection {
|
||||||
pub len: usize,
|
pub len: usize,
|
||||||
pub is_entire_line: bool,
|
pub is_entire_line: bool,
|
||||||
|
@ -356,7 +356,7 @@ fn is_identifier_char(c: char) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_vim_operator_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 {
|
fn skip_whitespace(source: &str) -> &str {
|
||||||
|
@ -38,10 +38,18 @@ impl ModeIndicator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn current_operators_description(&self, vim: &Vim) -> String {
|
fn current_operators_description(&self, vim: &Vim) -> String {
|
||||||
|
vim.state()
|
||||||
|
.pre_count
|
||||||
|
.map(|count| format!("{}", count))
|
||||||
|
.into_iter()
|
||||||
|
.chain(vim.state().selected_register.map(|reg| format!("\"{reg}")))
|
||||||
|
.chain(
|
||||||
vim.state()
|
vim.state()
|
||||||
.operator_stack
|
.operator_stack
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| item.id())
|
.map(|item| item.id().to_string()),
|
||||||
|
)
|
||||||
|
.chain(vim.state().post_count.map(|count| format!("{}", count)))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("")
|
.join("")
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,11 @@ use serde::Deserialize;
|
|||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use workspace::Workspace;
|
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)]
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -29,7 +33,7 @@ pub(crate) fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>
|
|||||||
|
|
||||||
fn system_clipboard_is_newer(vim: &Vim, cx: &mut AppContext) -> bool {
|
fn system_clipboard_is_newer(vim: &Vim, cx: &mut AppContext) -> bool {
|
||||||
cx.read_from_clipboard().is_some_and(|item| {
|
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()
|
last_state != item.text()
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
@ -46,20 +50,23 @@ fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext<Workspace>) {
|
|||||||
editor.transact(cx, |editor, cx| {
|
editor.transact(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
|
|
||||||
let (clipboard_text, clipboard_selections): (String, Option<_>) =
|
let (clipboard_text, clipboard_selections): (String, Option<_>) = if let Some(
|
||||||
if VimSettings::get_global(cx).use_system_clipboard == UseSystemClipboard::Never
|
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
|
|| VimSettings::get_global(cx).use_system_clipboard
|
||||||
== UseSystemClipboard::OnYank
|
== UseSystemClipboard::OnYank
|
||||||
&& !system_clipboard_is_newer(vim, cx)
|
&& !system_clipboard_is_newer(vim, cx)
|
||||||
{
|
{
|
||||||
(
|
(vim.read_register('"', None, cx).unwrap_or_default(), None)
|
||||||
vim.workspace_state
|
|
||||||
.registers
|
|
||||||
.get("\"")
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| "".to_string()),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
if let Some(item) = cx.read_from_clipboard() {
|
if let Some(item) = cx.read_from_clipboard() {
|
||||||
let clipboard_selections = item
|
let clipboard_selections = item
|
||||||
@ -606,4 +613,119 @@ mod test {
|
|||||||
three
|
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::<VimSettings>(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::<VimSettings>(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::<VimSettings>(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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,10 +63,10 @@ pub enum Operator {
|
|||||||
Jump { line: bool },
|
Jump { line: bool },
|
||||||
Indent,
|
Indent,
|
||||||
Outdent,
|
Outdent,
|
||||||
|
|
||||||
Lowercase,
|
Lowercase,
|
||||||
Uppercase,
|
Uppercase,
|
||||||
OppositeCase,
|
OppositeCase,
|
||||||
|
Register,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
@ -89,6 +89,8 @@ pub struct EditorState {
|
|||||||
pub current_tx: Option<TransactionId>,
|
pub current_tx: Option<TransactionId>,
|
||||||
pub current_anchor: Option<Selection<Anchor>>,
|
pub current_anchor: Option<Selection<Anchor>>,
|
||||||
pub undo_modes: HashMap<TransactionId, Mode>,
|
pub undo_modes: HashMap<TransactionId, Mode>,
|
||||||
|
|
||||||
|
pub selected_register: Option<char>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
@ -123,7 +125,7 @@ pub struct WorkspaceState {
|
|||||||
pub recorded_actions: Vec<ReplayableAction>,
|
pub recorded_actions: Vec<ReplayableAction>,
|
||||||
pub recorded_selection: RecordedSelection,
|
pub recorded_selection: RecordedSelection,
|
||||||
|
|
||||||
pub registers: HashMap<String, String>,
|
pub registers: HashMap<char, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -277,6 +279,7 @@ impl Operator {
|
|||||||
Operator::Uppercase => "gU",
|
Operator::Uppercase => "gU",
|
||||||
Operator::Lowercase => "gu",
|
Operator::Lowercase => "gu",
|
||||||
Operator::OppositeCase => "g~",
|
Operator::OppositeCase => "g~",
|
||||||
|
Operator::Register => "\"",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -287,6 +290,7 @@ impl Operator {
|
|||||||
| Operator::Mark
|
| Operator::Mark
|
||||||
| Operator::Jump { .. }
|
| Operator::Jump { .. }
|
||||||
| Operator::FindBackward { .. }
|
| Operator::FindBackward { .. }
|
||||||
|
| Operator::Register
|
||||||
| Operator::Replace
|
| Operator::Replace
|
||||||
| Operator::AddSurrounds { target: Some(_) }
|
| Operator::AddSurrounds { target: Some(_) }
|
||||||
| Operator::ChangeSurrounds { .. }
|
| Operator::ChangeSurrounds { .. }
|
||||||
|
@ -10,7 +10,7 @@ use language::language_settings::{AllLanguageSettings, SoftWrap};
|
|||||||
use util::test::marked_text_offsets;
|
use util::test::marked_text_offsets;
|
||||||
|
|
||||||
use super::{neovim_connection::NeovimConnection, VimTestContext};
|
use super::{neovim_connection::NeovimConnection, VimTestContext};
|
||||||
use crate::state::Mode;
|
use crate::{state::Mode, Vim};
|
||||||
|
|
||||||
pub struct NeovimBackedTestContext {
|
pub struct NeovimBackedTestContext {
|
||||||
cx: VimTestContext,
|
cx: VimTestContext,
|
||||||
@ -94,6 +94,7 @@ impl SharedState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct SharedClipboard {
|
pub struct SharedClipboard {
|
||||||
|
register: char,
|
||||||
neovim: String,
|
neovim: String,
|
||||||
editor: String,
|
editor: String,
|
||||||
state: SharedState,
|
state: SharedState,
|
||||||
@ -120,15 +121,17 @@ impl SharedClipboard {
|
|||||||
{}
|
{}
|
||||||
# currently expected:
|
# currently expected:
|
||||||
{}
|
{}
|
||||||
# neovim clipboard:
|
# neovim register \"{}:
|
||||||
{}
|
{}
|
||||||
# zed clipboard:
|
# zed register \"{}:
|
||||||
{}"},
|
{}"},
|
||||||
message,
|
message,
|
||||||
self.state.initial,
|
self.state.initial,
|
||||||
self.state.recent_keystrokes,
|
self.state.recent_keystrokes,
|
||||||
expected,
|
expected,
|
||||||
|
self.register,
|
||||||
self.neovim,
|
self.neovim,
|
||||||
|
self.register,
|
||||||
self.editor
|
self.editor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -241,12 +244,30 @@ impl NeovimBackedTestContext {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub async fn shared_clipboard(&mut self) -> SharedClipboard {
|
pub async fn shared_clipboard(&mut self) -> SharedClipboard {
|
||||||
SharedClipboard {
|
SharedClipboard {
|
||||||
|
register: '"',
|
||||||
state: self.shared_state().await,
|
state: self.shared_state().await,
|
||||||
neovim: self.neovim.read_register('"').await,
|
neovim: self.neovim.read_register('"').await,
|
||||||
editor: self.read_from_clipboard().unwrap().text().clone(),
|
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]
|
#[must_use]
|
||||||
pub async fn shared_state(&mut self) -> SharedState {
|
pub async fn shared_state(&mut self) -> SharedState {
|
||||||
let (mode, marked_text) = self.neovim.state().await;
|
let (mode, marked_text) = self.neovim.state().await;
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use editor::{ClipboardSelection, Editor};
|
use editor::{ClipboardSelection, Editor};
|
||||||
use gpui::{ClipboardItem, ViewContext};
|
use gpui::ViewContext;
|
||||||
use language::{CharKind, Point};
|
use language::{CharKind, Point};
|
||||||
use multi_buffer::MultiBufferRow;
|
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;
|
pub struct HighlightOnYank;
|
||||||
|
|
||||||
@ -102,21 +103,8 @@ fn copy_selections_content_internal(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let setting = VimSettings::get_global(cx).use_system_clipboard;
|
vim.write_registers(is_yank, linewise, text, clipboard_selections, cx);
|
||||||
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);
|
|
||||||
if !is_yank || vim.state().mode == Mode::Visual {
|
if !is_yank || vim.state().mode == Mode::Visual {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,11 @@ use collections::HashMap;
|
|||||||
use command_palette_hooks::{CommandPaletteFilter, CommandPaletteInterceptor};
|
use command_palette_hooks::{CommandPaletteFilter, CommandPaletteInterceptor};
|
||||||
use editor::{
|
use editor::{
|
||||||
movement::{self, FindRange},
|
movement::{self, FindRange},
|
||||||
Anchor, Bias, Editor, EditorEvent, EditorMode, ToPoint,
|
Anchor, Bias, ClipboardSelection, Editor, EditorEvent, EditorMode, ToPoint,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, impl_actions, Action, AppContext, EntityId, FocusableView, Global, KeystrokeEvent,
|
actions, impl_actions, Action, AppContext, ClipboardItem, EntityId, FocusableView, Global,
|
||||||
Subscription, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
|
KeystrokeEvent, Subscription, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use language::{CursorShape, Point, SelectionGoal, TransactionId};
|
use language::{CursorShape, Point, SelectionGoal, TransactionId};
|
||||||
pub use mode_indicator::ModeIndicator;
|
pub use mode_indicator::ModeIndicator;
|
||||||
@ -45,6 +45,7 @@ use state::{EditorState, Mode, Operator, RecordedSelection, WorkspaceState};
|
|||||||
use std::{ops::Range, sync::Arc};
|
use std::{ops::Range, sync::Arc};
|
||||||
use surrounds::{add_surrounds, change_surrounds, delete_surrounds};
|
use surrounds::{add_surrounds, change_surrounds, delete_surrounds};
|
||||||
use ui::BorrowAppContext;
|
use ui::BorrowAppContext;
|
||||||
|
use utils::SYSTEM_CLIPBOARD;
|
||||||
use visual::{visual_block_motion, visual_replace};
|
use visual::{visual_block_motion, visual_replace};
|
||||||
use workspace::{self, Workspace};
|
use workspace::{self, Workspace};
|
||||||
|
|
||||||
@ -70,6 +71,9 @@ pub struct PushOperator(pub Operator);
|
|||||||
#[derive(Clone, Deserialize, PartialEq)]
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
struct Number(usize);
|
struct Number(usize);
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
|
struct SelectRegister(String);
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
vim,
|
vim,
|
||||||
[
|
[
|
||||||
@ -86,7 +90,7 @@ actions!(
|
|||||||
// in the workspace namespace so it's not filtered out when vim is disabled.
|
// in the workspace namespace so it's not filtered out when vim is disabled.
|
||||||
actions!(workspace, [ToggleVimMode]);
|
actions!(workspace, [ToggleVimMode]);
|
||||||
|
|
||||||
impl_actions!(vim, [SwitchMode, PushOperator, Number]);
|
impl_actions!(vim, [SwitchMode, PushOperator, Number, SelectRegister]);
|
||||||
|
|
||||||
/// Initializes the `vim` crate.
|
/// Initializes the `vim` crate.
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
@ -129,7 +133,6 @@ fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
|||||||
workspace.register_action(|_: &mut Workspace, n: &Number, cx: _| {
|
workspace.register_action(|_: &mut Workspace, n: &Number, cx: _| {
|
||||||
Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
|
Vim::update(cx, |vim, cx| vim.push_count_digit(n.0, cx));
|
||||||
});
|
});
|
||||||
|
|
||||||
workspace.register_action(|_: &mut Workspace, _: &Tab, cx| {
|
workspace.register_action(|_: &mut Workspace, _: &Tab, cx| {
|
||||||
Vim::active_editor_input_ignored(" ".into(), cx)
|
Vim::active_editor_input_ignored(" ".into(), cx)
|
||||||
});
|
});
|
||||||
@ -202,7 +205,8 @@ fn observe_keystrokes(keystroke_event: &KeystrokeEvent, cx: &mut WindowContext)
|
|||||||
| Operator::ChangeSurrounds { .. }
|
| Operator::ChangeSurrounds { .. }
|
||||||
| Operator::DeleteSurrounds
|
| Operator::DeleteSurrounds
|
||||||
| Operator::Mark
|
| Operator::Mark
|
||||||
| Operator::Jump { .. },
|
| Operator::Jump { .. }
|
||||||
|
| Operator::Register,
|
||||||
) => {}
|
) => {}
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
vim.clear_operator(cx);
|
vim.clear_operator(cx);
|
||||||
@ -531,6 +535,138 @@ impl Vim {
|
|||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_register(&mut self, register: Arc<str>, 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<ClipboardSelection>,
|
||||||
|
cx: &mut ViewContext<Editor>,
|
||||||
|
) {
|
||||||
|
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<String> {
|
||||||
|
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::<Point>(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) {
|
fn push_operator(&mut self, operator: Operator, cx: &mut WindowContext) {
|
||||||
if matches!(
|
if matches!(
|
||||||
operator,
|
operator,
|
||||||
@ -573,7 +709,10 @@ impl Vim {
|
|||||||
|
|
||||||
fn clear_operator(&mut self, cx: &mut WindowContext) {
|
fn clear_operator(&mut self, cx: &mut WindowContext) {
|
||||||
self.take_count(cx);
|
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);
|
self.sync_vim_settings(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -741,6 +880,9 @@ impl Vim {
|
|||||||
Some(Operator::Mark) => Vim::update(cx, |vim, cx| {
|
Some(Operator::Mark) => Vim::update(cx, |vim, cx| {
|
||||||
normal::mark::create_mark(vim, text, false, 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),
|
Some(Operator::Jump { line }) => normal::mark::jump(text, line, cx),
|
||||||
_ => match Vim::read(cx).state().mode {
|
_ => match Vim::read(cx).state().mode {
|
||||||
Mode::Replace => multi_replace(text, cx),
|
Mode::Replace => multi_replace(text, cx),
|
||||||
|
26
crates/vim/test_data/test_named_registers.json
Normal file
26
crates/vim/test_data/test_named_registers.json
Normal file
@ -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"}}
|
45
crates/vim/test_data/test_numbered_registers.json
Normal file
45
crates/vim/test_data/test_numbered_registers.json
Normal file
@ -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"}}
|
30
crates/vim/test_data/test_special_registers.json
Normal file
30
crates/vim/test_data/test_special_registers.json
Normal file
@ -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"}}
|
@ -215,9 +215,9 @@ Some vim settings are available to modify the default vim behavior:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"vim": {
|
"vim": {
|
||||||
// "always": use system clipboard
|
// "always": use system clipboard when no register is specified
|
||||||
// "never": don't use system clipboard
|
// "never": don't use system clipboard unless "+ or "* is specified
|
||||||
// "on_yank": use system clipboard for yank operations
|
// "on_yank": use system clipboard for yank operations when no register is specified
|
||||||
"use_system_clipboard": "always",
|
"use_system_clipboard": "always",
|
||||||
// Lets `f` and `t` motions extend across multiple lines
|
// Lets `f` and `t` motions extend across multiple lines
|
||||||
"use_multiline_find": true
|
"use_multiline_find": true
|
||||||
|
Loading…
Reference in New Issue
Block a user