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.rs335
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 {}