diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index b67e2c8a38e2..8871eda55e9f 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -18,7 +18,6 @@ pub mod movement; pub mod object; pub mod path; mod position; -pub mod register; pub mod search; pub mod selection; pub mod shellwords; diff --git a/helix-core/src/register.rs b/helix-core/src/register.rs deleted file mode 100644 index df68a75943e6..000000000000 --- a/helix-core/src/register.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::collections::HashMap; - -#[derive(Debug)] -pub struct Register { - name: char, - values: Vec, -} - -impl Register { - pub const fn new(name: char) -> Self { - Self { - name, - values: Vec::new(), - } - } - - pub fn new_with_values(name: char, values: Vec) -> Self { - Self { name, values } - } - - pub const fn name(&self) -> char { - self.name - } - - pub fn read(&self) -> &[String] { - &self.values - } - - pub fn write(&mut self, values: Vec) { - self.values = values; - } - - pub fn push(&mut self, value: String) { - self.values.push(value); - } -} - -/// Currently just wraps a `HashMap` of `Register`s -#[derive(Debug, Default)] -pub struct Registers { - inner: HashMap, -} - -impl Registers { - pub fn get(&self, name: char) -> Option<&Register> { - self.inner.get(&name) - } - - pub fn read(&self, name: char) -> Option<&[String]> { - self.get(name).map(|reg| reg.read()) - } - - pub fn write(&mut self, name: char, values: Vec) { - if name != '_' { - self.inner - .insert(name, Register::new_with_values(name, values)); - } - } - - pub fn push(&mut self, name: char, value: String) { - if name != '_' { - if let Some(r) = self.inner.get_mut(&name) { - r.push(value); - } else { - self.write(name, vec![value]); - } - } - } - - pub fn first(&self, name: char) -> Option<&String> { - self.read(name).and_then(|entries| entries.first()) - } - - pub fn last(&self, name: char) -> Option<&String> { - self.read(name).and_then(|entries| entries.last()) - } - - pub fn inner(&self) -> &HashMap { - &self.inner - } - - pub fn clear(&mut self) { - self.inner.clear(); - } - - pub fn remove(&mut self, name: char) -> Option { - self.inner.remove(&name) - } -} diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index b54d6835a5af..a51dfebb7d04 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -11,6 +11,7 @@ use helix_view::{ document::DocumentSavedEventResult, editor::{ConfigEvent, EditorEvent}, graphics::Rect, + register::Registers, theme, tree::Layout, Align, Editor, @@ -64,6 +65,7 @@ pub struct Application { compositor: Compositor, terminal: Terminal, pub editor: Editor, + registers: Registers, config: Arc>, @@ -237,6 +239,7 @@ impl Application { compositor, terminal, editor, + registers: Registers::default(), config, @@ -255,6 +258,7 @@ impl Application { async fn render(&mut self) { let mut cx = crate::compositor::Context { editor: &mut self.editor, + registers: &mut self.registers, jobs: &mut self.jobs, scroll: None, }; @@ -505,6 +509,7 @@ impl Application { pub async fn handle_idle_timeout(&mut self) { let mut cx = crate::compositor::Context { editor: &mut self.editor, + registers: &mut self.registers, jobs: &mut self.jobs, scroll: None, }; @@ -625,6 +630,7 @@ impl Application { ) { let mut cx = crate::compositor::Context { editor: &mut self.editor, + registers: &mut self.registers, jobs: &mut self.jobs, scroll: None, }; diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 882a8a1dcc4a..8287aa790639 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -37,6 +37,7 @@ use helix_view::{ info::Info, input::KeyEvent, keyboard::KeyCode, + register::Registers, tree, view::View, Document, DocumentId, Editor, ViewId, @@ -83,6 +84,7 @@ pub struct Context<'a> { pub register: Option, pub count: Option, pub editor: &'a mut Editor, + pub registers: &'a mut Registers, pub callback: Option, pub on_next_key_callback: Option, @@ -189,6 +191,7 @@ impl MappableCommand { if let Some(command) = typed::TYPABLE_COMMAND_MAP.get(name.as_str()) { let mut cx = compositor::Context { editor: cx.editor, + registers: cx.registers, jobs: cx.jobs, scroll: None, }; @@ -1828,11 +1831,13 @@ fn search_impl( fn search_completions(cx: &mut Context, reg: Option) -> Vec { let mut items = reg - .and_then(|reg| cx.editor.registers.get(reg)) - .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect()); + .and_then(|reg| cx.registers.read(reg, cx.editor)) + .map_or(Vec::new(), |contents| { + contents.iter().take(200).cloned().collect() + }); items.sort_unstable(); items.dedup(); - items.into_iter().cloned().collect() + items.into_iter().collect() } fn search(cx: &mut Context) { @@ -1891,9 +1896,8 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir let count = cx.count(); let config = cx.editor.config(); let scrolloff = config.scrolloff; - let (_, doc) = current!(cx.editor); - let registers = &cx.editor.registers; - if let Some(query) = registers.read('/').and_then(|query| query.last()) { + if let Some(query) = cx.registers.last('/', cx.editor) { + let doc = doc!(cx.editor); let contents = doc.text().slice(..).to_string(); let search_config = &config.search; let case_insensitive = if search_config.smart_case { @@ -1902,7 +1906,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir false }; let wrap_around = search_config.wrap_around; - if let Ok(regex) = RegexBuilder::new(query) + if let Ok(regex) = RegexBuilder::new(&query) .case_insensitive(case_insensitive) .multi_line(true) .build() @@ -1955,12 +1959,14 @@ fn search_selection(cx: &mut Context) { .join("|"); let msg = format!("register '{}' set to '{}'", '/', ®ex); - cx.editor.registers.push('/', regex); - cx.editor.set_status(msg); + match cx.registers.push('/', cx.editor, regex) { + Ok(_) => cx.editor.set_status(msg), + Err(err) => cx.editor.set_error(err.to_string()), + } } fn make_search_word_bounded(cx: &mut Context) { - let regex = match cx.editor.registers.last('/') { + let regex = match cx.registers.last('/', cx.editor) { Some(regex) => regex, None => return, }; @@ -1978,14 +1984,16 @@ fn make_search_word_bounded(cx: &mut Context) { if !start_anchored { new_regex.push_str("\\b"); } - new_regex.push_str(regex); + new_regex.push_str(®ex); if !end_anchored { new_regex.push_str("\\b"); } let msg = format!("register '{}' set to '{}'", '/', &new_regex); - cx.editor.registers.push('/', new_regex); - cx.editor.set_status(msg); + match cx.registers.push('/', cx.editor, regex) { + Ok(_) => cx.editor.set_status(msg), + Err(err) => cx.editor.set_error(err.to_string()), + } } fn global_search(cx: &mut Context) { @@ -2295,20 +2303,24 @@ enum Operation { } fn delete_selection_impl(cx: &mut Context, op: Operation) { - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); - let selection = doc.selection(view.id); + let selection = doc.selection(view.id).clone(); if cx.register != Some('_') { // first yank the selection let text = doc.text().slice(..); let values: Vec = selection.fragments(text).map(Cow::into_owned).collect(); let reg_name = cx.register.unwrap_or('"'); - cx.editor.registers.write(reg_name, values); + if let Err(err) = cx.registers.write(reg_name, cx.editor, values) { + cx.editor.set_error(err.to_string()); + } }; + let (view, doc) = current!(cx.editor); + // then delete - let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { + let transaction = Transaction::change_by_selection(doc.text(), &selection, |range| { (range.from(), range.to(), None) }); doc.apply(&transaction, view.id); @@ -2677,6 +2689,7 @@ pub fn command_palette(cx: &mut Context) { register: None, count: std::num::NonZeroUsize::new(1), editor: cx.editor, + registers: cx.registers, callback: None, on_next_key_callback: None, jobs: cx.jobs, @@ -3616,11 +3629,14 @@ fn yank(cx: &mut Context) { cx.register.unwrap_or('"') ); - cx.editor + match cx .registers - .write(cx.register.unwrap_or('"'), values); + .write(cx.register.unwrap_or('"'), cx.editor, values) + { + Ok(_) => cx.editor.set_status(msg), + Err(err) => cx.editor.set_error(err.to_string()), + } - cx.editor.set_status(msg); exit_select_mode(cx); } @@ -3859,10 +3875,10 @@ fn paste_primary_clipboard_before(cx: &mut Context) { fn replace_with_yanked(cx: &mut Context) { let count = cx.count(); let reg_name = cx.register.unwrap_or('"'); - let (view, doc) = current!(cx.editor); - let registers = &mut cx.editor.registers; - if let Some(values) = registers.read(reg_name) { + if let Some(values) = cx.registers.read(reg_name, cx.editor) { + let (view, doc) = current!(cx.editor); + if !values.is_empty() { let repeat = std::iter::repeat( values @@ -3928,11 +3944,11 @@ fn replace_selections_with_primary_clipboard(cx: &mut Context) { fn paste(cx: &mut Context, pos: Paste) { let count = cx.count(); let reg_name = cx.register.unwrap_or('"'); - let (view, doc) = current!(cx.editor); - let registers = &mut cx.editor.registers; - if let Some(values) = registers.read(reg_name) { - paste_impl(values, doc, view, pos, count, cx.editor.mode); + if let Some(values) = cx.registers.read(reg_name, cx.editor) { + let (view, doc) = current!(cx.editor); + + paste_impl(&values, doc, view, pos, count, cx.editor.mode); } } @@ -4639,7 +4655,7 @@ fn wonly(cx: &mut Context) { } fn select_register(cx: &mut Context) { - cx.editor.autoinfo = Some(Info::from_registers(&cx.editor.registers)); + cx.editor.autoinfo = Some(Info::from_registers(cx.registers)); cx.on_next_key(move |cx, event| { if let Some(ch) = event.char() { cx.editor.autoinfo = None; @@ -4649,7 +4665,7 @@ fn select_register(cx: &mut Context) { } fn insert_register(cx: &mut Context) { - cx.editor.autoinfo = Some(Info::from_registers(&cx.editor.registers)); + cx.editor.autoinfo = Some(Info::from_registers(cx.registers)); cx.on_next_key(move |cx, event| { if let Some(ch) = event.char() { cx.editor.autoinfo = None; @@ -5362,9 +5378,12 @@ fn record_macro(cx: &mut Context) { } }) .collect::(); - cx.editor.registers.write(reg, vec![s]); - cx.editor - .set_status(format!("Recorded to register [{}]", reg)); + match cx.registers.write(reg, cx.editor, vec![s]) { + Ok(_) => cx + .editor + .set_status(format!("Recorded to register [{}]", reg)), + Err(err) => cx.editor.set_error(err.to_string()), + } } else { let reg = cx.register.take().unwrap_or('@'); cx.editor.macro_recording = Some((reg, Vec::new())); @@ -5384,8 +5403,13 @@ fn replay_macro(cx: &mut Context) { return; } - let keys: Vec = if let Some([keys_str]) = cx.editor.registers.read(reg) { - match helix_view::input::parse_macro(keys_str) { + let keys: Vec = if let Some(keys_str) = cx + .registers + .read(reg, cx.editor) + .filter(|values| values.len() == 1) + .map(|values| values[0].clone()) + { + match helix_view::input::parse_macro(&keys_str) { Ok(keys) => keys, Err(err) => { cx.editor.set_error(format!("Invalid macro: {}", err)); diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index fe92798baae6..417754319150 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2178,7 +2178,7 @@ fn clear_register( ensure!(args.len() <= 1, ":clear-register takes at most 1 argument"); if args.is_empty() { - cx.editor.registers.clear(); + cx.registers.clear(); cx.editor.set_status("All registers cleared"); return Ok(()); } @@ -2188,7 +2188,7 @@ fn clear_register( format!("Invalid register {}", args[0]) ); let register = args[0].chars().next().unwrap_or_default(); - match cx.editor.registers.remove(register) { + match cx.registers.remove(register) { Some(_) => cx .editor .set_status(format!("Register {} cleared", register)), diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index bcb3e44904e4..71acd4ba3090 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -2,7 +2,10 @@ // Q: how does this work with popups? // cursive does compositor.screen_mut().add_layer_at(pos::absolute(x, y), ) use helix_core::Position; -use helix_view::graphics::{CursorKind, Rect}; +use helix_view::{ + graphics::{CursorKind, Rect}, + register::Registers, +}; use tui::buffer::Buffer as Surface; @@ -22,6 +25,7 @@ pub use helix_view::input::Event; pub struct Context<'a> { pub editor: &'a mut Editor, + pub registers: &'a mut Registers, pub scroll: Option, pub jobs: &'a mut Jobs, } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index fd8e8fb21b47..bad7d117e3cb 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1207,6 +1207,7 @@ impl Component for EditorView { ) -> EventResult { let mut cx = commands::Context { editor: context.editor, + registers: context.registers, count: None, register: None, callback: None, @@ -1262,6 +1263,7 @@ impl Component for EditorView { // use a fake context here let mut cx = Context { editor: cx.editor, + registers: cx.registers, jobs: cx.jobs, scroll: None, }; diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index a9ccfb7378aa..7df0f7706073 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -297,7 +297,7 @@ impl Prompt { direction: CompletionDirection, ) { (self.callback_fn)(cx, &self.line, PromptEvent::Abort); - let values = match cx.editor.registers.read(register) { + let values = match cx.registers.read(register, cx.editor) { Some(values) if !values.is_empty() => values, _ => return, }; @@ -458,7 +458,7 @@ impl Prompt { // latest value in the register list match self .history_register - .and_then(|reg| cx.editor.registers.last(reg)) + .and_then(|reg| cx.registers.last(reg, cx.editor)) .map(|entry| entry.into()) { Some(value) => (value, true), @@ -558,7 +558,7 @@ impl Component for Prompt { } else { let last_item = self .history_register - .and_then(|reg| cx.editor.registers.last(reg).cloned()) + .and_then(|reg| cx.registers.last(reg, cx.editor)) .map(|entry| entry.into()) .unwrap_or_else(|| Cow::from("")); @@ -569,7 +569,11 @@ impl Component for Prompt { if last_item != self.line { // store in history if let Some(register) = self.history_register { - cx.editor.registers.push(register, self.line.clone()); + if let Err(err) = + cx.registers.push(register, cx.editor, self.line.clone()) + { + cx.editor.set_error(err.to_string()) + } }; } @@ -606,27 +610,16 @@ impl Component for Prompt { ctrl!('q') => self.exit_selection(), ctrl!('r') => { self.completion = cx - .editor .registers - .inner() - .iter() - .map(|(ch, reg)| { - let content = reg - .read() - .get(0) - .and_then(|s| s.lines().next().to_owned()) - .unwrap_or_default(); - (0.., format!("{} {}", ch, &content).into()) - }) + .iter_preview() + .map(|(ch, preview)| (0.., format!("{} {}", ch, &preview).into())) .collect(); self.next_char_handler = Some(Box::new(|prompt, c, context| { prompt.insert_str( - context - .editor + &context .registers - .read(c) - .and_then(|r| r.first()) - .map_or("", |r| r.as_str()), + .first(c, context.editor) + .unwrap_or_default(), context.editor, ); })); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 8e4dab414c99..7c06088cfa22 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -40,7 +40,6 @@ use tokio::{ use anyhow::{anyhow, bail, Error}; pub use helix_core::diagnostic::Severity; -pub use helix_core::register::Registers; use helix_core::{ auto_pairs::AutoPairs, syntax::{self, AutoPairConfig, SoftWrap}, @@ -814,7 +813,6 @@ pub struct Editor { pub count: Option, pub selected_register: Option, - pub registers: Registers, pub macro_recording: Option<(char, Vec)>, pub macro_replaying: Vec, pub language_servers: helix_lsp::Registry, @@ -965,7 +963,6 @@ impl Editor { theme_loader, last_theme: None, last_selection: None, - registers: Registers::default(), clipboard_provider: get_clipboard_provider(), status_msg: None, autoinfo: None, diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index 3080cf8e1b2f..372d799054c0 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -1,5 +1,5 @@ -use crate::input::KeyEvent; -use helix_core::{register::Registers, unicode::width::UnicodeWidthStr}; +use crate::{input::KeyEvent, register::Registers}; +use helix_core::unicode::width::UnicodeWidthStr; use std::{collections::BTreeSet, fmt::Write}; #[derive(Debug)] @@ -69,16 +69,8 @@ impl Info { pub fn from_registers(registers: &Registers) -> Self { let body: Vec<_> = registers - .inner() - .iter() - .map(|(ch, reg)| { - let content = reg - .read() - .get(0) - .and_then(|s| s.lines().next()) - .unwrap_or_default(); - (ch.to_string(), content) - }) + .iter_preview() + .map(|(ch, reg)| (ch.to_string(), reg)) .collect(); let mut infobox = Self::new("Registers", &body);