on master help sign in
Preview the latest value for regular registers

This fixes a discrepancy between regular registers which are used for yanking multiple values (for example via `"ay`) and regular registers that store a history of values (for example `"a*`). Previously, the preview shown in `select_register`'s infobox would show the oldest value in history. It's intuitive and useful to see the most recent value pushed to the history though. We cannot simply switch the preview line from `values.first()` to `values.last()`: that would fix the preview for registers used for history but break the preview for registers used to yank multiple values. We could push to the beginning of the values with `Registers::push` but this is wasteful from a performance perspective. Instead we can have `Registers::read` return an iterator that returns elements in the reverse order and reverse the values in `Register::write`. This effectively means that `push` adds elements to the beginning of the register's values. For the sake of the preview, we can switch to `values.last()` and that is then correct for both usage- styles. This also needs a change to call-sites that read the latest history value to switch from `last` to `first`.

commit2d838d729cca26a718e3536e8d75c60396420dbd authorMichael Davis <mcarsondavis@gmail.com> dateMon 10 Jul 2023, 19:06 (3 years ago) parent86a1f01
3 files changed+24−16
helix-term/src/commands.rs+2−2 helix-term/src/ui/prompt.rs+3−3 helix-view/src/register.rs+19−11
helix-term/src/commands.rs+2−2
@@ -1911,7 +1911,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
19111911 let register = cx.register.unwrap_or('/');
19121912 let config = cx.editor.config();
19131913 let scrolloff = config.scrolloff;
1914− if let Some(query) = cx.editor.registers.last(register, cx.editor) {
1914+ if let Some(query) = cx.editor.registers.first(register, cx.editor) {
19151915 let doc = doc!(cx.editor);
19161916 let contents = doc.text().slice(..).to_string();
19171917 let search_config = &config.search;
@@ -1983,7 +1983,7 @@ fn search_selection(cx: &mut Context) {
19831983
19841984fn make_search_word_bounded(cx: &mut Context) {
19851985 let register = cx.register.unwrap_or('/');
1986− let regex = match cx.editor.registers.last(register, cx.editor) {
1986+ let regex = match cx.editor.registers.first(register, cx.editor) {
19871987 Some(regex) => regex,
19881988 None => return,
19891989 };
helix-term/src/ui/prompt.rs+3−3
@@ -307,7 +307,7 @@ impl Prompt {
307307 ) {
308308 (self.callback_fn)(cx, &self.line, PromptEvent::Abort);
309309 let mut values = match cx.editor.registers.read(register, cx.editor) {
310− Some(values) if values.len() > 0 => values,
310+ Some(values) if values.len() > 0 => values.rev(),
311311 _ => return,
312312 };
313313
@@ -473,7 +473,7 @@ impl Prompt {
473473 // Show the most recently entered value as a suggestion.
474474 if let Some(suggestion) = self
475475 .history_register
476− .and_then(|reg| cx.editor.registers.last(reg, cx.editor))
476+ .and_then(|reg| cx.editor.registers.first(reg, cx.editor))
477477 {
478478 surface.set_string(line_area.x, line_area.y, suggestion, suggestion_color);
479479 }
@@ -570,7 +570,7 @@ impl Component for Prompt {
570570 } else {
571571 let last_item = self
572572 .history_register
573− .and_then(|reg| cx.editor.registers.last(reg, cx.editor))
573+ .and_then(|reg| cx.editor.registers.first(reg, cx.editor))
574574 .map(|entry| entry.to_string())
575575 .unwrap_or_else(|| String::from(""));
576576
helix-view/src/register.rs+19−11
@@ -23,6 +23,10 @@ use crate::{
2323/// * Primary clipboard (`+`)
2424#[derive(Debug)]
2525pub struct Registers {
26+ /// The mapping of register to values.
27+ /// Values are stored in reverse order when inserted with `Registers::write`.
28+ /// The order is reversed again in `Registers::read`. This allows us to
29+ /// efficiently prepend new values in `Registers::push`.
2630 inner: HashMap<char, Vec<String>>,
2731 clipboard_provider: Box<dyn ClipboardProvider>,
2832}
@@ -77,11 +81,11 @@ impl Registers {
7781 _ => self
7882 .inner
7983 .get(&name)
80− .map(|values| RegisterValues::new(values.iter().map(Cow::from))),
84+ .map(|values| RegisterValues::new(values.iter().map(Cow::from).rev())),
8185 }
8286 }
8387
84− pub fn write(&mut self, name: char, values: Vec<String>) -> Result<()> {
88+ pub fn write(&mut self, name: char, mut values: Vec<String>) -> Result<()> {
8589 match name {
8690 '_' => Ok(()),
8791 '#' | '.' | '%' => Err(anyhow::anyhow!("Register {name} does not support writing")),
@@ -94,17 +98,19 @@ impl Registers {
9498 _ => unreachable!(),
9599 },
96100 )?;
101+ values.reverse();
97102 self.inner.insert(name, values);
98103 Ok(())
99104 }
100105 _ => {
106+ values.reverse();
101107 self.inner.insert(name, values);
102108 Ok(())
103109 }
104110 }
105111 }
106112
107− pub fn push(&mut self, name: char, value: String) -> Result<()> {
113+ pub fn push(&mut self, name: char, mut value: String) -> Result<()> {
108114 match name {
109115 '_' => Ok(()),
110116 '#' | '.' | '%' => Err(anyhow::anyhow!("Register {name} does not support pushing")),
@@ -121,11 +127,13 @@ impl Registers {
121127 anyhow::bail!("Failed to push to register {name}: clipboard does not match register contents");
122128 }
123129
124− saved_values.push(value);
125− self.clipboard_provider.set_contents(
126− saved_values.join(NATIVE_LINE_ENDING.as_str()),
127− clipboard_type,
128− )?;
130+ saved_values.push(value.clone());
131+ if !contents.is_empty() {
132+ value.push_str(NATIVE_LINE_ENDING.as_str());
133+ }
134+ value.push_str(&contents);
135+ self.clipboard_provider
136+ .set_contents(value, clipboard_type)?;
129137
130138 Ok(())
131139 }
@@ -150,7 +158,7 @@ impl Registers {
150158 .filter(|(name, _)| !matches!(name, '*' | '+'))
151159 .map(|(name, values)| {
152160 let preview = values
153− .first()
161+ .last()
154162 .and_then(|s| s.lines().next())
155163 .unwrap_or("<empty>");
156164
@@ -222,7 +230,7 @@ fn read_from_clipboard<'a>(
222230 let Some(values) = saved_values else { return RegisterValues::new(iter::once(contents.into())) };
223231
224232 if contents_are_saved(values, &contents) {
225− RegisterValues::new(values.iter().map(Cow::from))
233+ RegisterValues::new(values.iter().map(Cow::from).rev())
226234 } else {
227235 RegisterValues::new(iter::once(contents.into()))
228236 }
@@ -243,7 +251,7 @@ fn read_from_clipboard<'a>(
243251
244252fn contents_are_saved(saved_values: &[String], mut contents: &str) -> bool {
245253 let line_ending = NATIVE_LINE_ENDING.as_str();
246− let mut values = saved_values.iter();
254+ let mut values = saved_values.iter().rev();
247255
248256 match values.next() {
249257 Some(first) if contents.starts_with(first) => {