diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index ee174e69d1cd8..f96459fdf668e 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -17,7 +17,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 6a4719ba43dbc..0000000000000 --- a/helix-core/src/register.rs +++ /dev/null @@ -1,85 +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 clear(&mut self, name: char) { - self.inner.remove(&name); - } - - 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 - } -} diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 14e952024202f..81e113a81cdd6 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1683,12 +1683,18 @@ fn search_impl( } fn search_completions(cx: &mut Context, reg: Option) -> Vec { + let doc = doc!(cx.editor); 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.editor.registers.read(doc, reg)) + .map_or(Vec::new(), |mut content| { + for s in &mut content { + s.drain(200..); + } + content + }); items.sort_unstable(); items.dedup(); - items.into_iter().cloned().collect() + items.into_iter().map(String::from).collect() } fn search(cx: &mut Context) { @@ -1749,7 +1755,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir 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) = registers.last(doc, '/') { let contents = doc.text().slice(..).to_string(); let search_config = &config.search; let case_insensitive = if search_config.smart_case { @@ -1758,7 +1764,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() @@ -1816,7 +1822,8 @@ fn search_selection(cx: &mut Context) { } fn make_search_word_bounded(cx: &mut Context) { - let regex = match cx.editor.registers.last('/') { + let doc = doc!(cx.editor); + let regex = match cx.editor.registers.last(doc, '/') { Some(regex) => regex, None => return, }; @@ -1834,7 +1841,7 @@ 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"); } @@ -3696,7 +3703,7 @@ fn replace_with_yanked(cx: &mut Context) { let (view, doc) = current!(cx.editor); let registers = &mut cx.editor.registers; - if let Some(values) = registers.read(reg_name) { + if let Some(values) = registers.read(doc, reg_name) { if !values.is_empty() { let repeat = std::iter::repeat( values @@ -3765,8 +3772,8 @@ fn paste(cx: &mut Context, pos: Paste) { 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) = registers.read(doc, reg_name) { + paste_impl(&values, doc, view, pos, count, cx.editor.mode); } } @@ -5144,6 +5151,7 @@ fn record_macro(cx: &mut Context) { } fn replay_macro(cx: &mut Context) { + let doc = doc!(cx.editor); let reg = cx.register.unwrap_or('@'); if cx.editor.macro_replaying.contains(®) { @@ -5154,18 +5162,19 @@ 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) { - Ok(keys) => keys, - Err(err) => { - cx.editor.set_error(format!("Invalid macro: {}", err)); - return; + let keys: Vec = + if let Some([keys_str]) = cx.editor.registers.read(doc, reg).as_deref() { + match helix_view::input::parse_macro(keys_str) { + Ok(keys) => keys, + Err(err) => { + cx.editor.set_error(format!("Invalid macro: {}", err)); + return; + } } - } - } else { - cx.editor.set_error(format!("Register [{}] empty", reg)); - return; - }; + } else { + cx.editor.set_error(format!("Register [{}] empty", reg)); + return; + }; // Once the macro has been fully validated, it's marked as being under replay // to ensure we don't fall into infinite recursion. diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 177e4db848b26..bfb3d28385d7b 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -294,7 +294,8 @@ impl Prompt { direction: CompletionDirection, ) { (self.callback_fn)(cx, &self.line, PromptEvent::Abort); - let values = match cx.editor.registers.read(register) { + let doc = doc!(cx.editor); + let values = match cx.editor.registers.read(doc, register) { Some(values) if !values.is_empty() => values, _ => return, }; @@ -451,11 +452,12 @@ impl Prompt { // render buffer text surface.set_string(area.x, area.y + line, &self.prompt, prompt_color); + let doc = doc!(cx.editor); let (input, is_suggestion): (Cow, bool) = if self.line.is_empty() { // latest value in the register list match self .history_register - .and_then(|reg| cx.editor.registers.last(reg)) + .and_then(|reg| cx.editor.registers.last(doc, reg)) .map(|entry| entry.into()) { Some(value) => (value, true), @@ -543,9 +545,10 @@ impl Component for Prompt { if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) { self.recalculate_completion(cx.editor); } else { + let doc = doc!(cx.editor); let last_item = self .history_register - .and_then(|reg| cx.editor.registers.last(reg).cloned()) + .and_then(|reg| cx.editor.registers.last(doc, reg)) .map(|entry| entry.into()) .unwrap_or_else(|| Cow::from("")); @@ -596,26 +599,14 @@ impl Component for Prompt { 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, content)| (0.., format!("{} {}", ch, &content).into())) .collect(); - self.next_char_handler = Some(Box::new(|prompt, c, context| { + self.next_char_handler = Some(Box::new(|prompt, c, cx| { + let doc = doc!(cx.editor); prompt.insert_str( - context - .editor - .registers - .read(c) - .and_then(|r| r.first()) - .map_or("", |r| r.as_str()), - context.editor, + &cx.editor.registers.first(doc, c).unwrap_or_default(), + cx.editor, ); })); (self.callback_fn)(cx, &self.line, PromptEvent::Update); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 59dea79bc4035..169ccb76455e1 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -5,6 +5,7 @@ use crate::{ graphics::{CursorKind, Rect}, info::Info, input::KeyEvent, + register::Registers, theme::{self, Theme}, tree::{self, Tree}, Align, Document, DocumentId, View, ViewId, @@ -37,7 +38,6 @@ use tokio::{ use anyhow::{anyhow, bail, Error}; pub use helix_core::diagnostic::Severity; -pub use helix_core::register::Registers; use helix_core::Position; use helix_core::{ auto_pairs::AutoPairs, diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index 3080cf8e1b2ff..e42afdf108b81 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -1,5 +1,6 @@ use crate::input::KeyEvent; -use helix_core::{register::Registers, unicode::width::UnicodeWidthStr}; +use crate::register::Registers; +use helix_core::unicode::width::UnicodeWidthStr; use std::{collections::BTreeSet, fmt::Write}; #[derive(Debug)] @@ -69,16 +70,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, content)| (ch.to_string(), content)) .collect(); let mut infobox = Self::new("Registers", &body); diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index 9cf36ae0505c7..fcce1cd27b472 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -15,6 +15,7 @@ pub mod base64; pub mod info; pub mod input; pub mod keyboard; +pub mod register; pub mod theme; pub mod tree; pub mod view; diff --git a/helix-view/src/register.rs b/helix-view/src/register.rs new file mode 100644 index 0000000000000..a5297103da8b6 --- /dev/null +++ b/helix-view/src/register.rs @@ -0,0 +1,110 @@ +use crate::document::Document; +use std::collections::btree_map::Entry; +use std::collections::BTreeMap; +use std::fmt; + +struct Readonly(String, Box Option>); + +impl fmt::Debug for Readonly { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Readonly").field(&self.0).finish() + } +} + +#[derive(Debug)] +enum Register { + Static(Vec), + Readonly(Readonly), +} + +/// Currently just wraps a `BTreeMap` of `Register`s +#[derive(Debug)] +pub struct Registers { + inner: BTreeMap, +} + +impl Registers { + pub fn read(&self, doc: &Document, name: char) -> Option> { + self.inner.get(&name).and_then(|reg| match reg { + Register::Static(content) => Some(content.clone()), + Register::Readonly(Readonly(_, func)) => func(doc).map(|content| vec![content]), + }) + } + + pub fn write(&mut self, name: char, values: Vec) { + match self.inner.entry(name) { + Entry::Vacant(v) => { + v.insert(Register::Static(values)); + } + Entry::Occupied(mut o) => { + let v = o.get_mut(); + match v { + Register::Static(_) => *v = Register::Static(values), + Register::Readonly(_) => {} + } + } + } + } + + pub fn clear(&mut self, name: char) { + self.inner.remove(&name); + } + + pub fn push(&mut self, name: char, value: String) { + if name != '_' { + if let Some(r) = self.inner.get_mut(&name) { + match r { + Register::Static(content) => content.push(value), + Register::Readonly(_) => {} + } + } else { + self.write(name, vec![value]); + } + } + } + + pub fn first(&self, doc: &Document, name: char) -> Option { + self.inner.get(&name).and_then(|reg| match reg { + Register::Static(content) => content.first().cloned(), + Register::Readonly(Readonly(_, func)) => func(doc), + }) + } + + pub fn last(&self, doc: &Document, name: char) -> Option { + self.inner.get(&name).and_then(|reg| match reg { + Register::Static(content) => content.last().cloned(), + Register::Readonly(Readonly(_, func)) => func(doc), + }) + } + + pub fn iter_preview(&self) -> impl Iterator { + self.inner.iter().map(|(&ch, reg)| { + let preview = match reg { + Register::Static(content) => content + .get(0) + .and_then(|s| s.lines().next()) + .unwrap_or_default(), + Register::Readonly(Readonly(name, _)) => name, + }; + (ch, preview) + }) + } +} + +impl Default for Registers { + fn default() -> Registers { + let mut inner = BTreeMap::new(); + inner.insert( + '_', + Register::Readonly(Readonly("null register".to_owned(), Box::new(|_doc| None))), + ); + inner.insert( + '%', + Register::Readonly(Readonly( + "buffer name".to_owned(), + Box::new(|doc| Some(doc.display_name().to_string())), + )), + ); + Registers { inner } + } +}