aboutsummaryrefslogtreecommitdiff
path: root/helix-view/src/register.rs
diff options
context:
space:
mode:
authorMichael Davis2023-07-10 21:30:42 +0000
committerBlaž Hrastnik2023-07-31 06:05:38 +0000
commit5eb1a25d8a0aeb202358b5b5a3b44bb5f43eced6 (patch)
treea29b19b4473c30458578ff9cf80a64bc6941528a /helix-view/src/register.rs
parent57952c46a4f3bf75d0b9940c2c42399980416523 (diff)
Refactor Registers to take Editor
This sets up a new Registers type that will allow us to expand support for special registers. (See the child commits.) We start simple with the regular (`Vec<String>`) registers and the simplest special register, the black hole. In the child commits we will expand these match arms with more special registers. The upcoming special registers will need a few things that aren't possible with the current Registers type in helix-core: * Access to the `Editor`. This is only necessary when reading from registers, so the `&Editor` parameter is only added to `Registers::read`. * Returning owned values. Registers in helix-core returns references to the values backed by the `Vec<String>` but future special registers will need to return owned values. We refactor the return value of the read operations to give `Cow<str>`s and iterators over those. * Returning a `Result` for write/push functions. This will be used by the clipboard special registers.
Diffstat (limited to 'helix-view/src/register.rs')
-rw-r--r--helix-view/src/register.rs136
1 files changed, 136 insertions, 0 deletions
diff --git a/helix-view/src/register.rs b/helix-view/src/register.rs
new file mode 100644
index 00000000..ca495ada
--- /dev/null
+++ b/helix-view/src/register.rs
@@ -0,0 +1,136 @@
+use std::{borrow::Cow, collections::HashMap, iter};
+
+use anyhow::Result;
+
+use crate::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
+#[derive(Debug, Default)]
+pub struct Registers {
+ inner: HashMap<char, Vec<String>>,
+}
+
+impl Registers {
+ pub fn read<'a>(&'a self, name: char, _editor: &'a Editor) -> Option<RegisterValues<'a>> {
+ match name {
+ '_' => Some(RegisterValues::new(iter::empty())),
+ _ => self
+ .inner
+ .get(&name)
+ .map(|values| RegisterValues::new(values.iter().map(Cow::from))),
+ }
+ }
+
+ pub fn write(&mut self, name: char, values: Vec<String>) -> Result<()> {
+ match name {
+ '_' => Ok(()),
+ _ => {
+ self.inner.insert(name, values);
+ Ok(())
+ }
+ }
+ }
+
+ pub fn push(&mut self, name: char, value: String) -> Result<()> {
+ match name {
+ '_' => Ok(()),
+ _ => {
+ self.inner.entry(name).or_insert_with(Vec::new).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(|values| values.last())
+ }
+
+ pub fn iter_preview(&self) -> impl Iterator<Item = (char, &str)> {
+ self.inner
+ .iter()
+ .map(|(name, values)| {
+ let preview = values
+ .first()
+ .and_then(|s| s.lines().next())
+ .unwrap_or("<empty>");
+
+ (*name, preview)
+ })
+ .chain([('_', "<empty>")].iter().copied())
+ }
+
+ pub fn clear(&mut self) {
+ self.inner.clear()
+ }
+
+ pub fn remove(&mut self, name: char) -> bool {
+ match name {
+ '_' => false,
+ _ => self.inner.remove(&name).is_some(),
+ }
+ }
+}
+
+// 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<'a> DoubleEndedIterator for RegisterValues<'a> {
+ fn next_back(&mut self) -> Option<Self::Item> {
+ self.iter.next_back()
+ }
+}
+
+impl<'a> ExactSizeIterator for RegisterValues<'a> {
+ 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 {}