From 68affa3c598723a8b9451ef3dcceda83ae161e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20CORTIER?= Date: Fri, 4 Jun 2021 22:21:31 -0400 Subject: [PATCH] Implement register selection User can select register to yank into with the " command. A new state is added to `Editor` and `commands::Context` structs. This state is managed by leveraging a new struct `RegisterSelection`. --- helix-core/src/register.rs | 13 ++++---- helix-term/src/commands.rs | 45 +++++++++++++++++--------- helix-term/src/keymap.rs | 2 ++ helix-term/src/ui/editor.rs | 8 +++-- helix-view/src/editor.rs | 4 ++- helix-view/src/lib.rs | 2 ++ helix-view/src/register_selection.rs | 47 ++++++++++++++++++++++++++++ 7 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 helix-view/src/register_selection.rs diff --git a/helix-core/src/register.rs b/helix-core/src/register.rs index 0be0ce89..0176d23e 100644 --- a/helix-core/src/register.rs +++ b/helix-core/src/register.rs @@ -6,16 +6,15 @@ use std::{collections::HashMap, sync::RwLock}; static REGISTRY: Lazy>>> = Lazy::new(|| RwLock::new(HashMap::new())); -pub fn get(register: char) -> Option> { +/// Read register values. +pub fn get(register_name: char) -> Option> { let registry = REGISTRY.read().unwrap(); - - // TODO: no cloning - registry.get(®ister).cloned() + registry.get(®ister_name).cloned() // TODO: no cloning } +/// Read register values. // restoring: bool -pub fn set(register: char, values: Vec) { +pub fn set(register_name: char, values: Vec) { let mut registry = REGISTRY.write().unwrap(); - - registry.insert(register, values); + registry.insert(register_name, values); } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 15fac7ad..94175006 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -35,6 +35,7 @@ use crossterm::event::{KeyCode, KeyEvent}; use once_cell::sync::Lazy; pub struct Context<'a> { + pub register: helix_view::RegisterSelection, pub count: usize, pub editor: &'a mut Editor, @@ -777,7 +778,7 @@ pub fn extend_line(cx: &mut Context) { // heuristic: append changes to history after each command, unless we're in insert mode -fn _delete_selection(doc: &mut Document, view_id: ViewId) { +fn _delete_selection(reg: char, doc: &mut Document, view_id: ViewId) { // first yank the selection let values: Vec = doc .selection(view_id) @@ -785,8 +786,6 @@ fn _delete_selection(doc: &mut Document, view_id: ViewId) { .map(Cow::into_owned) .collect(); - // TODO: allow specifying reg - let reg = '"'; register::set(reg, values); // then delete @@ -800,8 +799,9 @@ fn _delete_selection(doc: &mut Document, view_id: ViewId) { } pub fn delete_selection(cx: &mut Context) { + let reg = cx.register.name(); let (view, doc) = cx.current(); - _delete_selection(doc, view.id); + _delete_selection(reg, doc, view.id); doc.append_changes_to_history(view.id); @@ -810,8 +810,9 @@ pub fn delete_selection(cx: &mut Context) { } pub fn change_selection(cx: &mut Context) { + let reg = cx.register.name(); let (view, doc) = cx.current(); - _delete_selection(doc, view.id); + _delete_selection(reg, doc, view.id); enter_insert_mode(doc); } @@ -1893,11 +1894,13 @@ pub fn yank(cx: &mut Context) { .map(Cow::into_owned) .collect(); - // TODO: allow specifying reg - let reg = '"'; - let msg = format!("yanked {} selection(s) to register {}", values.len(), reg); + let msg = format!( + "yanked {} selection(s) to register {}", + values.len(), + cx.register.name() + ); - register::set(reg, values); + register::set(cx.register.name(), values); cx.editor.set_status(msg) } @@ -1908,9 +1911,7 @@ enum Paste { After, } -fn _paste(doc: &mut Document, view: &View, action: Paste) -> Option { - // TODO: allow specifying reg - let reg = '"'; +fn _paste(reg: char, doc: &mut Document, view: &View, action: Paste) -> Option { if let Some(values) = register::get(reg) { let repeat = std::iter::repeat( values @@ -1956,18 +1957,20 @@ fn _paste(doc: &mut Document, view: &View, action: Paste) -> Option // default insert pub fn paste_after(cx: &mut Context) { + let reg = cx.register.name(); let (view, doc) = cx.current(); - if let Some(transaction) = _paste(doc, view, Paste::After) { + if let Some(transaction) = _paste(reg, doc, view, Paste::After) { doc.apply(&transaction, view.id); doc.append_changes_to_history(view.id); } } pub fn paste_before(cx: &mut Context) { + let reg = cx.register.name(); let (view, doc) = cx.current(); - if let Some(transaction) = _paste(doc, view, Paste::Before) { + if let Some(transaction) = _paste(reg, doc, view, Paste::Before) { doc.apply(&transaction, view.id); doc.append_changes_to_history(view.id); } @@ -2426,6 +2429,18 @@ pub fn wclose(cx: &mut Context) { cx.editor.close(view_id, /* close_buffer */ false); } +pub fn select_register(cx: &mut Context) { + cx.on_next_key(move |cx, event| { + if let KeyEvent { + code: KeyCode::Char(ch), + .. + } = event + { + cx.editor.register.select(ch); + } + }) +} + pub fn space_mode(cx: &mut Context) { cx.on_next_key(move |cx, event| { if let KeyEvent { @@ -2439,7 +2454,7 @@ pub fn space_mode(cx: &mut Context) { 'b' => buffer_picker(cx), 'w' => window_mode(cx), // ' ' => toggle_alternate_buffer(cx), - // TODO: temporary since space mode took it's old key + // TODO: temporary since space mode took its old key ' ' => keep_primary_selection(cx), _ => (), } diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 27ef9b9e..d2fa46c7 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -284,6 +284,8 @@ pub fn default() -> Keymaps { key!(' ') => commands::space_mode, key!('z') => commands::view_mode, + + key!('"') => commands::select_register, ); // TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether // we keep this separate select mode. More keys can fit into normal mode then, but it's weird diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index cd66b703..4f485ed2 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -537,6 +537,9 @@ impl EditorView { // if this fails, count was Some(0) // debug_assert!(cxt.count != 0); + // set the register + cxt.register = cxt.editor.register.take(); + if let Some(command) = self.keymap[&mode].get(&event) { command(cxt); } @@ -575,11 +578,12 @@ impl Component for EditorView { let mode = doc.mode(); let mut cxt = commands::Context { - editor: &mut cx.editor, + register: helix_view::RegisterSelection::default(), count: 1, + editor: &mut cx.editor, callback: None, - callbacks: cx.callbacks, on_next_key_callback: None, + callbacks: cx.callbacks, }; if let Some(on_next_key) = self.on_next_key.take() { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index fa8dea2f..b69ae22f 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1,4 +1,4 @@ -use crate::{theme::Theme, tree::Tree, Document, DocumentId, View, ViewId}; +use crate::{theme::Theme, tree::Tree, Document, DocumentId, RegisterSelection, View, ViewId}; use tui::layout::Rect; use std::path::PathBuf; @@ -13,6 +13,7 @@ pub struct Editor { pub tree: Tree, pub documents: SlotMap, pub count: Option, + pub register: RegisterSelection, pub theme: Theme, pub language_servers: helix_lsp::Registry, @@ -57,6 +58,7 @@ impl Editor { tree: Tree::new(area), documents: SlotMap::with_key(), count: None, + register: RegisterSelection::default(), theme, language_servers, status_msg: None, diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index 00bddad6..7e253320 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -1,5 +1,6 @@ pub mod document; pub mod editor; +pub mod register_selection; pub mod theme; pub mod tree; pub mod view; @@ -10,5 +11,6 @@ new_key_type! { pub struct ViewId; } pub use document::Document; pub use editor::Editor; +pub use register_selection::RegisterSelection; pub use theme::Theme; pub use view::View; diff --git a/helix-view/src/register_selection.rs b/helix-view/src/register_selection.rs new file mode 100644 index 00000000..d7f073b6 --- /dev/null +++ b/helix-view/src/register_selection.rs @@ -0,0 +1,47 @@ +/// Register selection and configuration +/// +/// This is a kind a of specialized `Option` for register selection. +/// Point is to keep whether the register selection has been explicitely +/// set or not while being convenient by knowing the default register name. +pub struct RegisterSelection { + selected: char, + default_name: char, +} + +impl RegisterSelection { + pub fn new(default_name: char) -> Self { + Self { + selected: default_name, + default_name, + } + } + + pub fn select(&mut self, name: char) { + self.selected = name; + } + + pub fn take(&mut self) -> Self { + Self { + selected: std::mem::replace(&mut self.selected, self.default_name), + default_name: self.default_name, + } + } + + pub fn is_default(&self) -> bool { + self.selected == self.default_name + } + + pub fn name(&self) -> char { + self.selected + } +} + +impl Default for RegisterSelection { + fn default() -> Self { + let default_name = '"'; + Self { + selected: default_name, + default_name, + } + } +}