Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/register.rs')
| -rw-r--r-- | helix-view/src/register.rs | 335 |
1 files changed, 0 insertions, 335 deletions
diff --git a/helix-view/src/register.rs b/helix-view/src/register.rs deleted file mode 100644 index b2bb53ac..00000000 --- a/helix-view/src/register.rs +++ /dev/null @@ -1,335 +0,0 @@ -use std::{borrow::Cow, collections::HashMap, iter}; - -use anyhow::Result; -use arc_swap::access::DynAccess; -use helix_core::NATIVE_LINE_ENDING; - -use crate::{ - clipboard::{ClipboardError, ClipboardProvider, ClipboardType}, - Editor, -}; - -/// 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 (`+`) -pub struct Registers { - /// The mapping of register to values. - /// Values are stored in reverse order when inserted with `Registers::write`. - /// The order is reversed again in `Registers::read`. This allows us to - /// efficiently prepend new values in `Registers::push`. - inner: HashMap<char, Vec<String>>, - clipboard_provider: Box<dyn DynAccess<ClipboardProvider>>, - pub last_search_register: char, -} - -impl Registers { - pub fn new(clipboard_provider: Box<dyn DynAccess<ClipboardProvider>>) -> Self { - Self { - inner: Default::default(), - clipboard_provider, - last_search_register: '/', - } - } - - pub fn read<'a>(&'a self, name: char, editor: &'a Editor) -> Option<RegisterValues<'a>> { - 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<usize> but - // not RangeInclusive<usize>. - 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 path = doc!(editor).display_name(); - Some(RegisterValues::new(iter::once(path))) - } - '*' | '+' => Some(read_from_clipboard( - &self.clipboard_provider.load(), - self.inner.get(&name), - match name { - '+' => ClipboardType::Clipboard, - '*' => ClipboardType::Selection, - _ => unreachable!(), - }, - )), - _ => self - .inner - .get(&name) - .map(|values| RegisterValues::new(values.iter().map(Cow::from).rev())), - } - } - - pub fn write(&mut self, name: char, mut values: Vec<String>) -> Result<()> { - match name { - '_' => Ok(()), - '#' | '.' | '%' => Err(anyhow::anyhow!("Register {name} does not support writing")), - '*' | '+' => { - self.clipboard_provider.load().set_contents( - &values.join(NATIVE_LINE_ENDING.as_str()), - match name { - '+' => ClipboardType::Clipboard, - '*' => ClipboardType::Selection, - _ => unreachable!(), - }, - )?; - values.reverse(); - self.inner.insert(name, values); - Ok(()) - } - _ => { - values.reverse(); - self.inner.insert(name, values); - Ok(()) - } - } - } - - 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 - .load() - .get_contents(&clipboard_type)?; - let saved_values = self.inner.entry(name).or_default(); - - if !contents_are_saved(saved_values, &contents) { - anyhow::bail!("Failed to push to register {name}: clipboard does not match register contents"); - } - - saved_values.push(value.clone()); - if !contents.is_empty() { - value.push_str(NATIVE_LINE_ENDING.as_str()); - } - value.push_str(&contents); - self.clipboard_provider - .load() - .set_contents(&value, clipboard_type)?; - - Ok(()) - } - _ => { - self.inner.entry(name).or_default().push(value); - Ok(()) - } - } - } - - pub fn first<'a>(&'a self, name: char, editor: &'a Editor) -> Option<Cow<'a, str>> { - self.read(name, editor).and_then(|mut values| values.next()) - } - - pub fn last<'a>(&'a self, name: char, editor: &'a Editor) -> Option<Cow<'a, str>> { - self.read(name, editor) - .and_then(|mut values| values.next_back()) - } - - pub fn iter_preview(&self) -> impl Iterator<Item = (char, &str)> { - self.inner - .iter() - .filter(|(name, _)| !matches!(name, '*' | '+')) - .map(|(name, values)| { - let preview = values - .last() - .and_then(|s| s.lines().next()) - .unwrap_or("<empty>"); - - (*name, preview) - }) - .chain( - [ - ('_', "<empty>"), - ('#', "<selection indices>"), - ('.', "<selection contents>"), - ('%', "<document path>"), - ('+', "<system clipboard>"), - ('*', "<primary clipboard>"), - ] - .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 - .load() - .set_contents("", clipboard_type) - { - log::error!( - "Failed to clear {} clipboard: {err}", - match clipboard_type { - ClipboardType::Clipboard => "system", - ClipboardType::Selection => "primary", - } - ) - } - } - - pub fn clipboard_provider_name(&self) -> String { - self.clipboard_provider.load().name().into_owned() - } -} - -fn read_from_clipboard<'a>( - provider: &ClipboardProvider, - saved_values: Option<&'a Vec<String>>, - 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(values) = saved_values else { - return RegisterValues::new(iter::once(contents.into())); - }; - - if contents_are_saved(values, &contents) { - RegisterValues::new(values.iter().map(Cow::from).rev()) - } else { - RegisterValues::new(iter::once(contents.into())) - } - } - Err(ClipboardError::ReadingNotSupported) => match saved_values { - Some(values) => RegisterValues::new(values.iter().map(Cow::from).rev()), - None => RegisterValues::new(iter::empty()), - }, - 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(saved_values: &[String], mut contents: &str) -> bool { - let line_ending = NATIVE_LINE_ENDING.as_str(); - let mut values = saved_values.iter().rev(); - - 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<dyn DoubleEndedExactSizeIterator<Item = Cow<'a, str>> + 'a>, -} - -impl<'a> RegisterValues<'a> { - fn new( - iter: impl DoubleEndedIterator<Item = Cow<'a, str>> - + ExactSizeIterator<Item = Cow<'a, str>> - + 'a, - ) -> Self { - Self { - iter: Box::new(iter), - } - } -} - -impl<'a> Iterator for RegisterValues<'a> { - type Item = Cow<'a, str>; - - fn next(&mut self) -> Option<Self::Item> { - self.iter.next() - } - - fn size_hint(&self) -> (usize, Option<usize>) { - self.iter.size_hint() - } -} - -impl DoubleEndedIterator for RegisterValues<'_> { - fn next_back(&mut self) -> Option<Self::Item> { - self.iter.next_back() - } -} - -impl ExactSizeIterator for RegisterValues<'_> { - fn len(&self) -> usize { - self.iter.len() - } -} - -// Each RegisterValues iterator is both double ended and exact size. We can't -// type RegisterValues as `Box<dyn DoubleEndedIterator + ExactSizeIterator>` -// 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<I: DoubleEndedIterator + ExactSizeIterator> DoubleEndedExactSizeIterator for I {} |