diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index e1b5a1a1..9a512eae 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 df68a759..00000000 --- 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/commands.rs b/helix-term/src/commands.rs index 58c17296..d9ea580d 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1847,11 +1847,11 @@ 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.editor.registers.read(reg, cx.editor)) + .map_or(Vec::new(), |reg| reg.take(200).collect()); items.sort_unstable(); items.dedup(); - items.into_iter().cloned().collect() + items.into_iter().map(|value| value.to_string()).collect() } fn search(cx: &mut Context) { @@ -1910,9 +1910,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.editor.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 { @@ -1921,7 +1920,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() @@ -1974,12 +1973,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.editor.registers.push('/', 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.editor.registers.last('/', cx.editor) { Some(regex) => regex, None => return, }; @@ -1997,14 +1998,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.editor.registers.push('/', new_regex) { + Ok(_) => cx.editor.set_status(msg), + Err(err) => cx.editor.set_error(err.to_string()), + } } fn global_search(cx: &mut Context) { @@ -2367,7 +2370,10 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) { 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.editor.registers.write(reg_name, values) { + cx.editor.set_error(err.to_string()); + return; + } }; // then delete @@ -3758,18 +3764,16 @@ fn yank(cx: &mut Context) { .fragments(text) .map(Cow::into_owned) .collect(); + let selections = values.len(); + let register = cx.register.unwrap_or('"'); - let msg = format!( - "yanked {} selection(s) to register {}", - values.len(), - cx.register.unwrap_or('"') - ); - - cx.editor - .registers - .write(cx.register.unwrap_or('"'), values); + match cx.editor.registers.write(register, values) { + Ok(_) => cx.editor.set_status(format!( + "yanked {selections} selection(s) to register {register}", + )), + Err(err) => cx.editor.set_error(err.to_string()), + } - cx.editor.set_status(msg); exit_select_mode(cx); } @@ -3778,6 +3782,7 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) { let text = doc.text().slice(..); let selection = doc.selection(view.id); + let selections = selection.len(); let joined = selection .fragments(text) .fold(String::new(), |mut acc, fragment| { @@ -3788,14 +3793,12 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) { acc }); - let msg = format!( - "joined and yanked {} selection(s) to register {}", - selection.len(), - register, - ); - - editor.registers.write(register, vec![joined]); - editor.set_status(msg); + match editor.registers.write(register, vec![joined]) { + Ok(_) => editor.set_status(format!( + "joined and yanked {selections} selection(s) to register {register}", + )), + Err(err) => editor.set_error(err.to_string()), + } } fn yank_joined(cx: &mut Context) { @@ -4040,34 +4043,34 @@ 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 !values.is_empty() { - let repeat = std::iter::repeat( - values - .last() - .map(|value| Tendril::from(&value.repeat(count))) - .unwrap(), - ); - let mut values = values - .iter() - .map(|value| Tendril::from(&value.repeat(count))) - .chain(repeat); - let selection = doc.selection(view.id); - let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { - if !range.is_empty() { - (range.from(), range.to(), Some(values.next().unwrap())) - } else { - (range.from(), range.to(), None) - } - }); - doc.apply(&transaction, view.id); - exit_select_mode(cx); + let Some(values) = cx.editor.registers + .read(reg_name, cx.editor) + .filter(|values| values.len() > 0) else { return }; + let values: Vec<_> = values.map(|value| value.to_string()).collect(); + + let (view, doc) = current!(cx.editor); + let repeat = std::iter::repeat( + values + .last() + .map(|value| Tendril::from(&value.repeat(count))) + .unwrap(), + ); + let mut values = values + .iter() + .map(|value| Tendril::from(&value.repeat(count))) + .chain(repeat); + let selection = doc.selection(view.id); + let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { + if !range.is_empty() { + (range.from(), range.to(), Some(values.next().unwrap())) + } else { + (range.from(), range.to(), None) } - } + }); + + doc.apply(&transaction, view.id); + exit_select_mode(cx); } fn replace_selections_with_clipboard_impl( @@ -4109,12 +4112,12 @@ 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); - } + let Some(values) = cx.editor.registers.read(reg_name, cx.editor) else { return }; + let values: Vec<_> = values.map(|value| value.to_string()).collect(); + + let (view, doc) = current!(cx.editor); + paste_impl(&values, doc, view, pos, count, cx.editor.mode); } fn paste_after(cx: &mut Context) { @@ -5593,9 +5596,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.editor.registers.write(reg, 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())); @@ -5615,8 +5621,14 @@ 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) = cx + .editor + .registers + .read(reg, cx.editor) + .filter(|values| values.len() == 1) + .map(|mut values| values.next().unwrap()) + { + match helix_view::input::parse_macro(&keys) { 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 175f8bc6..28759b3f 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -2285,13 +2285,12 @@ fn clear_register( format!("Invalid register {}", args[0]) ); let register = args[0].chars().next().unwrap_or_default(); - match cx.editor.registers.remove(register) { - Some(_) => cx - .editor - .set_status(format!("Register {} cleared", register)), - None => cx - .editor - .set_error(format!("Register {} not found", register)), + if cx.editor.registers.remove(register) { + cx.editor + .set_status(format!("Register {} cleared", register)); + } else { + cx.editor + .set_error(format!("Register {} not found", register)); } Ok(()) } diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 1352f493..8dc2906a 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -306,8 +306,8 @@ impl Prompt { direction: CompletionDirection, ) { (self.callback_fn)(cx, &self.line, PromptEvent::Abort); - let values = match cx.editor.registers.read(register) { - Some(values) if !values.is_empty() => values, + let mut values = match cx.editor.registers.read(register, cx.editor) { + Some(values) if values.len() > 0 => values, _ => return, }; @@ -315,13 +315,16 @@ impl Prompt { let index = match direction { CompletionDirection::Forward => self.history_pos.map_or(0, |i| i + 1), - CompletionDirection::Backward => { - self.history_pos.unwrap_or(values.len()).saturating_sub(1) - } + CompletionDirection::Backward => self + .history_pos + .unwrap_or_else(|| values.len()) + .saturating_sub(1), } .min(end); - self.line = values[index].clone(); + self.line = values.nth(index).unwrap().to_string(); + // Appease the borrow checker. + drop(values); self.history_pos = Some(index); @@ -470,7 +473,7 @@ impl Prompt { // Show the most recently entered value as a suggestion. if let Some(suggestion) = self .history_register - .and_then(|reg| cx.editor.registers.last(reg)) + .and_then(|reg| cx.editor.registers.last(reg, cx.editor)) { surface.set_string(line_area.x, line_area.y, suggestion, suggestion_color); } @@ -567,25 +570,29 @@ impl Component for Prompt { } else { let last_item = self .history_register - .and_then(|reg| cx.editor.registers.last(reg).cloned()) - .map(|entry| entry.into()) - .unwrap_or_else(|| Cow::from("")); + .and_then(|reg| cx.editor.registers.last(reg, cx.editor)) + .map(|entry| entry.to_string()) + .unwrap_or_else(|| String::from("")); // handle executing with last command in history if nothing entered - let input: Cow = if self.line.is_empty() { - last_item + let input = if self.line.is_empty() { + &last_item } else { 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.editor.registers.push(register, self.line.clone()) + { + cx.editor.set_error(err.to_string()); + } }; } - self.line.as_str().into() + &self.line }; - (self.callback_fn)(cx, &input, PromptEvent::Validate); + (self.callback_fn)(cx, input, PromptEvent::Validate); return close_fn; } @@ -617,25 +624,16 @@ 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, preview)| (0.., format!("{} {}", ch, &preview).into())) .collect(); self.next_char_handler = Some(Box::new(|prompt, c, context| { prompt.insert_str( - context + &context .editor .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 20469ae9..1824ebf7 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}, view::ViewPosition, @@ -40,7 +41,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}, diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs index 1503e855..ca783fef 100644 --- a/helix-view/src/info.rs +++ b/helix-view/src/info.rs @@ -1,4 +1,5 @@ -use helix_core::{register::Registers, unicode::width::UnicodeWidthStr}; +use crate::register::Registers; +use helix_core::unicode::width::UnicodeWidthStr; use std::fmt::Write; #[derive(Debug)] @@ -56,16 +57,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, preview)| (ch.to_string(), preview)) .collect(); let mut infobox = Self::new("Registers", &body);