use std::{ borrow::Cow, collections::{vec_deque, HashMap, VecDeque}, iter, num::NonZeroUsize, }; use anyhow::Result; use helix_core::NATIVE_LINE_ENDING; use crate::{ clipboard::{get_clipboard_provider, ClipboardProvider, ClipboardType}, document::SCRATCH_BUFFER_NAME, Editor, }; /// Standard registers store up to this many yanks worth of history. /// Once a register hits this many yanks, it discards the oldest values to /// make space for new yanks. const MAX_REGISTER_HISTORY_LEN: usize = 100; #[cfg_attr(test, derive(Clone))] #[derive(Debug, Default)] struct Register { /// The values held by the register. /// /// When yanking to a register, all values are pushed into this `VecDeque`. The length /// of those values is stored in `length`. So each yank is stored in this flat sequence, /// but this `VecDeque` holds the sequence of all yanks. /// /// This `VecDeque` should only be empty when constructing `Register` via `Default`, which /// we do in `Registers` for simplicity. (Note that it should be impossible to `write` /// with an empty set of values.) /// /// Yanks are stored least to most recent. Within each yank, values are stored in order. values: VecDeque, /// The length of each yank into the register. lengths: VecDeque, } impl Register { fn latest_value(&self) -> Option<&String> { self.values.back() } fn values(&self) -> RegisterValues<'_> { let length = self.lengths.back().map(|len| len.get()).unwrap_or_default(); RegisterValues::new( self.values .iter() .rev() .take(length) .rev() .map(|s| Cow::Borrowed(s.as_str())), ) } /// An iterator over the history of the register yanks from newest to oldest. fn history(&self) -> RegisterHistoryIter<'_> { RegisterHistoryIter { lengths: self.lengths.iter().enumerate().rev(), values: &self.values, cursor: self.values.len(), } } fn write>(&mut self, values: I) { // If the register is full, discard the oldest yank in history. if self.lengths.len() > MAX_REGISTER_HISTORY_LEN { // Greater than max length implies non-empty. let oldest_len = self.lengths.pop_front().unwrap(); self.values.drain(..oldest_len.get()); } let pre_yank_len = self.values.len(); self.values.extend(values.into_iter()); let yank_len = NonZeroUsize::new(self.values.len() - pre_yank_len) .expect("writes to registers must not be empty"); self.lengths.push_back(yank_len); } fn push(&mut self, value: String) { self.values.push_back(value); if let Some(last_length) = self.lengths.back_mut() { *last_length = NonZeroUsize::new(last_length.get() + 1).unwrap(); } else { self.lengths.push_back(NonZeroUsize::new(1).unwrap()); } } fn select_history_entry(&mut self, index: usize) { let Some(length) = self.lengths.remove(index) else { return; }; self.lengths.push_back(length); let start: usize = self.lengths.range(..index).map(|len| len.get()).sum(); // NOTE: due to borrow checker limitations we need to collect the drained values. // Ideally this would be `self.values.extend(self.values.drain(...))`. let mut entry: VecDeque<_> = self.values.drain(start..(start + length.get())).collect(); self.values.append(&mut entry); } } /// A key-value store for saving sets of values. /// /// Each register corresponds to a `char`. Most chars can be used to store any set of /// values but a few chars are "special registers". Special registers have unique /// behaviors when read or written to: /// /// * Black hole (`_`): all values read and written are discarded /// * Selection indices (`#`): index number of each selection starting at 1 /// * Selection contents (`.`) /// * Document path (`%`): filename of the current buffer /// * System clipboard (`*`) /// * Primary clipboard (`+`) #[derive(Debug)] pub struct Registers { /// The mapping of register to values. /// This contains non-special registers plus '+' and '*'. inner: HashMap, clipboard_provider: Box, pub last_search_register: char, } impl Default for Registers { fn default() -> Self { Self { inner: Default::default(), clipboard_provider: get_clipboard_provider(), last_search_register: '/', } } } impl Registers { pub fn read<'a>(&'a self, name: char, editor: &'a Editor) -> Option> { match name { '_' => Some(RegisterValues::new(iter::empty())), '#' => { let (view, doc) = current_ref!(editor); let selections = doc.selection(view.id).len(); // ExactSizeIterator is implemented for Range but // not RangeInclusive. Some(RegisterValues::new( (0..selections).map(|i| (i + 1).to_string().into()), )) } '.' => { let (view, doc) = current_ref!(editor); let text = doc.text().slice(..); Some(RegisterValues::new(doc.selection(view.id).fragments(text))) } '%' => { let doc = doc!(editor); let path = doc .path() .as_ref() .map(|p| p.to_string_lossy()) .unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()); Some(RegisterValues::new(iter::once(path))) } '*' | '+' => Some(read_from_clipboard( self.clipboard_provider.as_ref(), self.inner.get(&name), match name { '+' => ClipboardType::Clipboard, '*' => ClipboardType::Selection, _ => unreachable!(), }, )), _ => self.inner.get(&name).map(Register::values), } } pub fn history(&self, name: char) -> Option> { match name { '_' | '#' | '.' | '%' => None, _ => self.inner.get(&name).map(Register::history), } } pub fn write>(&mut self, name: char, values: I) -> Result<()> { match name { '_' => Ok(()), '#' | '.' | '%' => Err(anyhow::anyhow!("Register {name} does not support writing")), _ => { self.inner.entry(name).or_default().write(values); self.sync_clipboard_register(name) } } } pub fn push(&mut self, name: char, mut value: String) -> Result<()> { match name { '_' => Ok(()), '#' | '.' | '%' => Err(anyhow::anyhow!("Register {name} does not support pushing")), '*' | '+' => { let clipboard_type = match name { '+' => ClipboardType::Clipboard, '*' => ClipboardType::Selection, _ => unreachable!(), }; let contents = self.clipboard_provider.get_contents(clipboard_type)?; let register = self.inner.entry(name).or_default(); if !contents_are_saved(register.values(), &contents) { anyhow::bail!("Failed to push to register {name}: clipboard does not match register contents"); } register.push(value.clone()); if !contents.is_empty() { value.push_str(NATIVE_LINE_ENDING.as_str()); } value.push_str(&contents); self.clipboard_provider .set_contents(value, clipboard_type)?; Ok(()) } _ => { self.inner.entry(name).or_default().push(value); Ok(()) } } } /// "Selects" the index at the given index for the given register. /// /// Selecting an item pulls it to the front of the register's history. /// /// If the register is a special register other than a clipboard register ('+' or '*') /// or if the index is out of bounds for the given register, this command is a no-op. pub fn select_history_entry(&mut self, name: char, index: usize) -> Result<()> { match name { '_' | '#' | '.' | '%' => { Err(anyhow::anyhow!("Register {name} does not support writing")) } _ => { let Some(register) = self.inner.get_mut(&name) else { return Ok(()); }; register.select_history_entry(index); self.sync_clipboard_register(name) } } } fn sync_clipboard_register(&mut self, name: char) -> Result<()> { let clipboard_type = match name { '+' => ClipboardType::Clipboard, '*' => ClipboardType::Selection, _ => return Ok(()), }; let mut contents = String::new(); for val in self.inner[&name].values() { if !contents.is_empty() { contents.push_str(NATIVE_LINE_ENDING.as_str()); } contents.push_str(&val); } self.clipboard_provider .set_contents(contents, clipboard_type) } /// Returns the latest value in the given register. /// /// The latest value is the value most recently pushed to the register when /// using `push`, or the last value returned by the iterator passed to [write]. pub fn latest<'a>(&'a self, name: char, editor: &'a Editor) -> Option> { self.read(name, editor).and_then(|values| values.last()) } /// Returns the oldest value in the given register. /// This is the opposite of `latest`. pub fn oldest<'a>(&'a self, name: char, editor: &'a Editor) -> Option> { self.read(name, editor).and_then(|mut values| values.next()) } pub fn iter_preview(&self) -> impl Iterator { self.inner .iter() .filter(|(name, _)| !matches!(name, '*' | '+')) .map(|(name, register)| { let preview = register .latest_value() .and_then(|s| s.lines().next()) .unwrap_or(""); (*name, preview) }) .chain( [ ('_', ""), ('#', ""), ('.', ""), ('%', ""), ('+', ""), ('*', ""), ] .iter() .copied(), ) } pub fn clear(&mut self) { self.clear_clipboard(ClipboardType::Clipboard); self.clear_clipboard(ClipboardType::Selection); self.inner.clear() } pub fn remove(&mut self, name: char) -> bool { match name { '*' | '+' => { self.clear_clipboard(match name { '+' => ClipboardType::Clipboard, '*' => ClipboardType::Selection, _ => unreachable!(), }); self.inner.remove(&name); true } '_' | '#' | '.' | '%' => false, _ => self.inner.remove(&name).is_some(), } } fn clear_clipboard(&mut self, clipboard_type: ClipboardType) { if let Err(err) = self .clipboard_provider .set_contents("".into(), clipboard_type) { log::error!( "Failed to clear {} clipboard: {err}", match clipboard_type { ClipboardType::Clipboard => "system", ClipboardType::Selection => "primary", } ) } } pub fn clipboard_provider_name(&self) -> Cow { self.clipboard_provider.name() } } fn read_from_clipboard<'a>( provider: &dyn ClipboardProvider, register: Option<&'a Register>, clipboard_type: ClipboardType, ) -> RegisterValues<'a> { match provider.get_contents(clipboard_type) { Ok(contents) => { // If we're pasting the same values that we just yanked, re-use // the saved values. This allows pasting multiple selections // even when yanked to a clipboard. let Some(register) = register else { return RegisterValues::new(iter::once(contents.into())); }; if contents_are_saved(register.values(), &contents) { register.values() } else { RegisterValues::new(iter::once(contents.into())) } } Err(err) => { log::error!( "Failed to read {} clipboard: {err}", match clipboard_type { ClipboardType::Clipboard => "system", ClipboardType::Selection => "primary", } ); RegisterValues::new(iter::empty()) } } } fn contents_are_saved(mut values: RegisterValues<'_>, mut contents: &str) -> bool { let line_ending = NATIVE_LINE_ENDING.as_str(); match values.next() { Some(first) if contents.starts_with(&*first) => { contents = &contents[first.len()..]; } None if contents.is_empty() => return true, _ => return false, } for value in values { if contents.starts_with(line_ending) && contents[line_ending.len()..].starts_with(&*value) { contents = &contents[line_ending.len() + value.len()..]; } else { return false; } } true } // This is a wrapper of an iterator that is both double ended and exact size, // and can return either owned or borrowed values. Regular registers can // return borrowed values while some special registers need to return owned // values. pub struct RegisterValues<'a> { iter: Box> + 'a>, } impl<'a> RegisterValues<'a> { fn new( iter: impl DoubleEndedIterator> + ExactSizeIterator> + 'a, ) -> Self { Self { iter: Box::new(iter), } } } impl<'a> Iterator for RegisterValues<'a> { type Item = Cow<'a, str>; fn next(&mut self) -> Option { self.iter.next() } fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } } impl<'a> DoubleEndedIterator for RegisterValues<'a> { fn next_back(&mut self) -> Option { self.iter.next_back() } } impl<'a> ExactSizeIterator for RegisterValues<'a> { fn len(&self) -> usize { self.iter.len() } } pub struct RegisterHistoryIter<'a> { lengths: iter::Rev>>, values: &'a VecDeque, cursor: usize, } impl<'a> Iterator for RegisterHistoryIter<'a> { // A concretion of `impl DoubleEndedExactSizeIterator`. type Item = (usize, vec_deque::Iter<'a, String>); fn next(&mut self) -> Option { let (index, length) = self.lengths.next()?; let length = length.get(); let values = self.values.range((self.cursor - length)..self.cursor); self.cursor -= length; Some((index, values)) } } // Each RegisterValues iterator is both double ended and exact size. We can't // type RegisterValues as `Box` // because only one non-auto trait is allowed in trait objects. So we need to // create a new trait that covers both. `RegisterValues` wraps that type so that // trait only needs to live in this module and not be imported for all register // callsites. trait DoubleEndedExactSizeIterator: DoubleEndedIterator + ExactSizeIterator {} impl DoubleEndedExactSizeIterator for I {} #[cfg(test)] mod test { use super::*; #[test] fn select_from_register_history_test() { let mut register = Register::default(); // Three yanks with 'abc' as the oldest, 'xyz' as the newest. register.write(["a".into()]); register.write(["b".into(), "c".into()]); register.write(["d".into(), "e".into(), "f".into()]); let history: Vec<_> = register .history() .map(|(index, values)| (index, values.cloned().collect::())) .collect(); assert_eq!( history, [(2usize, "def".into()), (1, "bc".into()), (0, "a".into())] ); let mut reg = register.clone(); reg.select_history_entry(0); let history: Vec<_> = reg .history() .map(|(_index, values)| values.cloned().collect::()) .collect(); assert_eq!(history, ["a", "def", "bc"]); let mut reg = register.clone(); reg.select_history_entry(1); let history: Vec<_> = reg .history() .map(|(_index, values)| values.cloned().collect::()) .collect(); assert_eq!(history, ["bc", "def", "a"]); // Choosing the current value is a no-op for regular registers. It will write the // value to the clipboard for clipboard registers though. let mut reg = register.clone(); reg.select_history_entry(2); let history: Vec<_> = reg .history() .map(|(_index, values)| values.cloned().collect::()) .collect(); assert_eq!(history, ["def", "bc", "a"]); // Providing an index outside of the bounds of the history is a no-op. let mut reg = register.clone(); reg.select_history_entry(3); let history: Vec<_> = reg .history() .map(|(_index, values)| values.cloned().collect::()) .collect(); assert_eq!(history, ["def", "bc", "a"]); } }