on master help sign in
Use refactored Registers type

This is an unfortunately noisy change: we need to update virtually all callsites that access the registers. For reads this means passing in the Editor and for writes this means handling potential failure when we can't write to a clipboard register.

commitbaceb02a090fe711ad772d855635bc590826fa53 authorMichael Davis <mcarsondavis@gmail.com> dateMon 10 Jul 2023, 18:48 (3 years ago) parent0f19f28
7 files changed+118−206
helix-core/src/lib.rs−1 helix-core/src/register.rs−89 helix-term/src/commands.rs+80−68 helix-term/src/commands/typed.rs+6−7 helix-term/src/ui/prompt.rs+27−29 helix-view/src/editor.rs+1−1 helix-view/src/info.rs+4−11
helix-core/src/lib.rs−1
@@ -18,7 +18,6 @@ pub mod movement;
1818pub mod object;
1919pub mod path;
2020mod position;
21−pub mod register;
2221pub mod search;
2322pub mod selection;
2423pub mod shellwords;
helix-core/src/register.rsdeleted−89
@@ -1,89 +0,0 @@
1−use std::collections::HashMap;
2−
3−#[derive(Debug)]
4−pub struct Register {
5− name: char,
6− values: Vec<String>,
7−}
8−
9−impl Register {
10− pub const fn new(name: char) -> Self {
11− Self {
12− name,
13− values: Vec::new(),
14− }
15− }
16−
17− pub fn new_with_values(name: char, values: Vec<String>) -> Self {
18− Self { name, values }
19− }
20−
21− pub const fn name(&self) -> char {
22− self.name
23− }
24−
25− pub fn read(&self) -> &[String] {
26− &self.values
27− }
28−
29− pub fn write(&mut self, values: Vec<String>) {
30− self.values = values;
31− }
32−
33− pub fn push(&mut self, value: String) {
34− self.values.push(value);
35− }
36−}
37−
38−/// Currently just wraps a `HashMap` of `Register`s
39−#[derive(Debug, Default)]
40−pub struct Registers {
41− inner: HashMap<char, Register>,
42−}
43−
44−impl Registers {
45− pub fn get(&self, name: char) -> Option<&Register> {
46− self.inner.get(&name)
47− }
48−
49− pub fn read(&self, name: char) -> Option<&[String]> {
50− self.get(name).map(|reg| reg.read())
51− }
52−
53− pub fn write(&mut self, name: char, values: Vec<String>) {
54− if name != '_' {
55− self.inner
56− .insert(name, Register::new_with_values(name, values));
57− }
58− }
59−
60− pub fn push(&mut self, name: char, value: String) {
61− if name != '_' {
62− if let Some(r) = self.inner.get_mut(&name) {
63− r.push(value);
64− } else {
65− self.write(name, vec![value]);
66− }
67− }
68− }
69−
70− pub fn first(&self, name: char) -> Option<&String> {
71− self.read(name).and_then(|entries| entries.first())
72− }
73−
74− pub fn last(&self, name: char) -> Option<&String> {
75− self.read(name).and_then(|entries| entries.last())
76− }
77−
78− pub fn inner(&self) -> &HashMap<char, Register> {
79− &self.inner
80− }
81−
82− pub fn clear(&mut self) {
83− self.inner.clear();
84− }
85−
86− pub fn remove(&mut self, name: char) -> Option<Register> {
87− self.inner.remove(&name)
88− }
89−}
helix-term/src/commands.rs+80−68
@@ -1847,11 +1847,11 @@ fn search_impl(
18471847
18481848fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
18491849 let mut items = reg
1850− .and_then(|reg| cx.editor.registers.get(reg))
1851− .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect());
1850+ .and_then(|reg| cx.editor.registers.read(reg, cx.editor))
1851+ .map_or(Vec::new(), |reg| reg.take(200).collect());
18521852 items.sort_unstable();
18531853 items.dedup();
1854− items.into_iter().cloned().collect()
1854+ items.into_iter().map(|value| value.to_string()).collect()
18551855}
18561856
18571857fn search(cx: &mut Context) {
@@ -1910,9 +1910,8 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
19101910 let count = cx.count();
19111911 let config = cx.editor.config();
19121912 let scrolloff = config.scrolloff;
1913− let (_, doc) = current!(cx.editor);
1914− let registers = &cx.editor.registers;
1915− if let Some(query) = registers.read('/').and_then(|query| query.last()) {
1913+ if let Some(query) = cx.editor.registers.last('/', cx.editor) {
1914+ let doc = doc!(cx.editor);
19161915 let contents = doc.text().slice(..).to_string();
19171916 let search_config = &config.search;
19181917 let case_insensitive = if search_config.smart_case {
@@ -1921,7 +1920,7 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
19211920 false
19221921 };
19231922 let wrap_around = search_config.wrap_around;
1924− if let Ok(regex) = RegexBuilder::new(query)
1923+ if let Ok(regex) = RegexBuilder::new(&query)
19251924 .case_insensitive(case_insensitive)
19261925 .multi_line(true)
19271926 .build()
@@ -1974,12 +1973,14 @@ fn search_selection(cx: &mut Context) {
19741973 .join("|");
19751974
19761975 let msg = format!("register '{}' set to '{}'", '/', &regex);
1977− cx.editor.registers.push('/', regex);
1978− cx.editor.set_status(msg);
1976+ match cx.editor.registers.push('/', regex) {
1977+ Ok(_) => cx.editor.set_status(msg),
1978+ Err(err) => cx.editor.set_error(err.to_string()),
1979+ }
19791980}
19801981
19811982fn make_search_word_bounded(cx: &mut Context) {
1982− let regex = match cx.editor.registers.last('/') {
1983+ let regex = match cx.editor.registers.last('/', cx.editor) {
19831984 Some(regex) => regex,
19841985 None => return,
19851986 };
@@ -1997,14 +1998,16 @@ fn make_search_word_bounded(cx: &mut Context) {
19971998 if !start_anchored {
19981999 new_regex.push_str("\\b");
19992000 }
2000− new_regex.push_str(regex);
2001+ new_regex.push_str(&regex);
20012002 if !end_anchored {
20022003 new_regex.push_str("\\b");
20032004 }
20042005
20052006 let msg = format!("register '{}' set to '{}'", '/', &new_regex);
2006− cx.editor.registers.push('/', new_regex);
2007− cx.editor.set_status(msg);
2007+ match cx.editor.registers.push('/', new_regex) {
2008+ Ok(_) => cx.editor.set_status(msg),
2009+ Err(err) => cx.editor.set_error(err.to_string()),
2010+ }
20082011}
20092012
20102013fn global_search(cx: &mut Context) {
@@ -2367,7 +2370,10 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
23672370 let text = doc.text().slice(..);
23682371 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
23692372 let reg_name = cx.register.unwrap_or('"');
2370− cx.editor.registers.write(reg_name, values);
2373+ if let Err(err) = cx.editor.registers.write(reg_name, values) {
2374+ cx.editor.set_error(err.to_string());
2375+ return;
2376+ }
23712377 };
23722378
23732379 // then delete
@@ -3758,18 +3764,16 @@ fn yank(cx: &mut Context) {
37583764 .fragments(text)
37593765 .map(Cow::into_owned)
37603766 .collect();
3767+ let selections = values.len();
3768+ let register = cx.register.unwrap_or('"');
37613769
3762− let msg = format!(
3763− "yanked {} selection(s) to register {}",
3764− values.len(),
3765− cx.register.unwrap_or('"')
3766− );
3767−
3768− cx.editor
3769− .registers
3770− .write(cx.register.unwrap_or('"'), values);
3770+ match cx.editor.registers.write(register, values) {
3771+ Ok(_) => cx.editor.set_status(format!(
3772+ "yanked {selections} selection(s) to register {register}",
3773+ )),
3774+ Err(err) => cx.editor.set_error(err.to_string()),
3775+ }
37713776
3772− cx.editor.set_status(msg);
37733777 exit_select_mode(cx);
37743778}
37753779
@@ -3778,6 +3782,7 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
37783782 let text = doc.text().slice(..);
37793783
37803784 let selection = doc.selection(view.id);
3785+ let selections = selection.len();
37813786 let joined = selection
37823787 .fragments(text)
37833788 .fold(String::new(), |mut acc, fragment| {
@@ -3788,14 +3793,12 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
37883793 acc
37893794 });
37903795
3791− let msg = format!(
3792− "joined and yanked {} selection(s) to register {}",
3793− selection.len(),
3794− register,
3795− );
3796−
3797− editor.registers.write(register, vec![joined]);
3798− editor.set_status(msg);
3796+ match editor.registers.write(register, vec![joined]) {
3797+ Ok(_) => editor.set_status(format!(
3798+ "joined and yanked {selections} selection(s) to register {register}",
3799+ )),
3800+ Err(err) => editor.set_error(err.to_string()),
3801+ }
37993802}
38003803
38013804fn yank_joined(cx: &mut Context) {
@@ -4040,34 +4043,34 @@ fn paste_primary_clipboard_before(cx: &mut Context) {
40404043fn replace_with_yanked(cx: &mut Context) {
40414044 let count = cx.count();
40424045 let reg_name = cx.register.unwrap_or('"');
4043− let (view, doc) = current!(cx.editor);
4044− let registers = &mut cx.editor.registers;
4045−
4046− if let Some(values) = registers.read(reg_name) {
4047− if !values.is_empty() {
4048− let repeat = std::iter::repeat(
4049− values
4050− .last()
4051− .map(|value| Tendril::from(&value.repeat(count)))
4052− .unwrap(),
4053− );
4054− let mut values = values
4055− .iter()
4056− .map(|value| Tendril::from(&value.repeat(count)))
4057− .chain(repeat);
4058− let selection = doc.selection(view.id);
4059− let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4060− if !range.is_empty() {
4061− (range.from(), range.to(), Some(values.next().unwrap()))
4062− } else {
4063− (range.from(), range.to(), None)
4064− }
4065− });
40664046
4067− doc.apply(&transaction, view.id);
4068− exit_select_mode(cx);
4047+ let Some(values) = cx.editor.registers
4048+ .read(reg_name, cx.editor)
4049+ .filter(|values| values.len() > 0) else { return };
4050+ let values: Vec<_> = values.map(|value| value.to_string()).collect();
4051+
4052+ let (view, doc) = current!(cx.editor);
4053+ let repeat = std::iter::repeat(
4054+ values
4055+ .last()
4056+ .map(|value| Tendril::from(&value.repeat(count)))
4057+ .unwrap(),
4058+ );
4059+ let mut values = values
4060+ .iter()
4061+ .map(|value| Tendril::from(&value.repeat(count)))
4062+ .chain(repeat);
4063+ let selection = doc.selection(view.id);
4064+ let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
4065+ if !range.is_empty() {
4066+ (range.from(), range.to(), Some(values.next().unwrap()))
4067+ } else {
4068+ (range.from(), range.to(), None)
40694069 }
4070− }
4070+ });
4071+
4072+ doc.apply(&transaction, view.id);
4073+ exit_select_mode(cx);
40714074}
40724075
40734076fn replace_selections_with_clipboard_impl(
@@ -4109,12 +4112,12 @@ fn replace_selections_with_primary_clipboard(cx: &mut Context) {
41094112fn paste(cx: &mut Context, pos: Paste) {
41104113 let count = cx.count();
41114114 let reg_name = cx.register.unwrap_or('"');
4112− let (view, doc) = current!(cx.editor);
4113− let registers = &mut cx.editor.registers;
41144115
4115− if let Some(values) = registers.read(reg_name) {
4116− paste_impl(values, doc, view, pos, count, cx.editor.mode);
4117− }
4116+ let Some(values) = cx.editor.registers.read(reg_name, cx.editor) else { return };
4117+ let values: Vec<_> = values.map(|value| value.to_string()).collect();
4118+
4119+ let (view, doc) = current!(cx.editor);
4120+ paste_impl(&values, doc, view, pos, count, cx.editor.mode);
41184121}
41194122
41204123fn paste_after(cx: &mut Context) {
@@ -5593,9 +5596,12 @@ fn record_macro(cx: &mut Context) {
55935596 }
55945597 })
55955598 .collect::<String>();
5596− cx.editor.registers.write(reg, vec![s]);
5597− cx.editor
5598− .set_status(format!("Recorded to register [{}]", reg));
5599+ match cx.editor.registers.write(reg, vec![s]) {
5600+ Ok(_) => cx
5601+ .editor
5602+ .set_status(format!("Recorded to register [{}]", reg)),
5603+ Err(err) => cx.editor.set_error(err.to_string()),
5604+ }
55995605 } else {
56005606 let reg = cx.register.take().unwrap_or('@');
56015607 cx.editor.macro_recording = Some((reg, Vec::new()));
@@ -5615,8 +5621,14 @@ fn replay_macro(cx: &mut Context) {
56155621 return;
56165622 }
56175623
5618− let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
5619− match helix_view::input::parse_macro(keys_str) {
5624+ let keys: Vec<KeyEvent> = if let Some(keys) = cx
5625+ .editor
5626+ .registers
5627+ .read(reg, cx.editor)
5628+ .filter(|values| values.len() == 1)
5629+ .map(|mut values| values.next().unwrap())
5630+ {
5631+ match helix_view::input::parse_macro(&keys) {
56205632 Ok(keys) => keys,
56215633 Err(err) => {
56225634 cx.editor.set_error(format!("Invalid macro: {}", err));
helix-term/src/commands/typed.rs+6−7
@@ -2285,13 +2285,12 @@ fn clear_register(
22852285 format!("Invalid register {}", args[0])
22862286 );
22872287 let register = args[0].chars().next().unwrap_or_default();
2288− match cx.editor.registers.remove(register) {
2289− Some(_) => cx
2290− .editor
2291− .set_status(format!("Register {} cleared", register)),
2292− None => cx
2293− .editor
2294− .set_error(format!("Register {} not found", register)),
2288+ if cx.editor.registers.remove(register) {
2289+ cx.editor
2290+ .set_status(format!("Register {} cleared", register));
2291+ } else {
2292+ cx.editor
2293+ .set_error(format!("Register {} not found", register));
22952294 }
22962295 Ok(())
22972296}
helix-term/src/ui/prompt.rs+27−29
@@ -306,8 +306,8 @@ impl Prompt {
306306 direction: CompletionDirection,
307307 ) {
308308 (self.callback_fn)(cx, &self.line, PromptEvent::Abort);
309− let values = match cx.editor.registers.read(register) {
310− Some(values) if !values.is_empty() => values,
309+ let mut values = match cx.editor.registers.read(register, cx.editor) {
310+ Some(values) if values.len() > 0 => values,
311311 _ => return,
312312 };
313313
@@ -315,13 +315,16 @@ impl Prompt {
315315
316316 let index = match direction {
317317 CompletionDirection::Forward => self.history_pos.map_or(0, |i| i + 1),
318− CompletionDirection::Backward => {
319− self.history_pos.unwrap_or(values.len()).saturating_sub(1)
320− }
318+ CompletionDirection::Backward => self
319+ .history_pos
320+ .unwrap_or_else(|| values.len())
321+ .saturating_sub(1),
321322 }
322323 .min(end);
323324
324− self.line = values[index].clone();
325+ self.line = values.nth(index).unwrap().to_string();
326+ // Appease the borrow checker.
327+ drop(values);
325328
326329 self.history_pos = Some(index);
327330
@@ -470,7 +473,7 @@ impl Prompt {
470473 // Show the most recently entered value as a suggestion.
471474 if let Some(suggestion) = self
472475 .history_register
473− .and_then(|reg| cx.editor.registers.last(reg))
476+ .and_then(|reg| cx.editor.registers.last(reg, cx.editor))
474477 {
475478 surface.set_string(line_area.x, line_area.y, suggestion, suggestion_color);
476479 }
@@ -567,25 +570,29 @@ impl Component for Prompt {
567570 } else {
568571 let last_item = self
569572 .history_register
570− .and_then(|reg| cx.editor.registers.last(reg).cloned())
571− .map(|entry| entry.into())
572− .unwrap_or_else(|| Cow::from(""));
573+ .and_then(|reg| cx.editor.registers.last(reg, cx.editor))
574+ .map(|entry| entry.to_string())
575+ .unwrap_or_else(|| String::from(""));
573576
574577 // handle executing with last command in history if nothing entered
575− let input: Cow<str> = if self.line.is_empty() {
576− last_item
578+ let input = if self.line.is_empty() {
579+ &last_item
577580 } else {
578581 if last_item != self.line {
579582 // store in history
580583 if let Some(register) = self.history_register {
581− cx.editor.registers.push(register, self.line.clone());
584+ if let Err(err) =
585+ cx.editor.registers.push(register, self.line.clone())
586+ {
587+ cx.editor.set_error(err.to_string());
588+ }
582589 };
583590 }
584591
585− self.line.as_str().into()
592+ &self.line
586593 };
587594
588− (self.callback_fn)(cx, &input, PromptEvent::Validate);
595+ (self.callback_fn)(cx, input, PromptEvent::Validate);
589596
590597 return close_fn;
591598 }
@@ -617,25 +624,16 @@ impl Component for Prompt {
617624 self.completion = cx
618625 .editor
619626 .registers
620− .inner()
621− .iter()
622− .map(|(ch, reg)| {
623− let content = reg
624− .read()
625− .get(0)
626− .and_then(|s| s.lines().next().to_owned())
627− .unwrap_or_default();
628− (0.., format!("{} {}", ch, &content).into())
629− })
627+ .iter_preview()
628+ .map(|(ch, preview)| (0.., format!("{} {}", ch, &preview).into()))
630629 .collect();
631630 self.next_char_handler = Some(Box::new(|prompt, c, context| {
632631 prompt.insert_str(
633− context
632+ &context
634633 .editor
635634 .registers
636− .read(c)
637− .and_then(|r| r.first())
638− .map_or("", |r| r.as_str()),
635+ .first(c, context.editor)
636+ .unwrap_or_default(),
639637 context.editor,
640638 );
641639 }));
helix-view/src/editor.rs+1−1
@@ -5,6 +5,7 @@ use crate::{
55 graphics::{CursorKind, Rect},
66 info::Info,
77 input::KeyEvent,
8+ register::Registers,
89 theme::{self, Theme},
910 tree::{self, Tree},
1011 view::ViewPosition,
@@ -40,7 +41,6 @@ use tokio::{
4041use anyhow::{anyhow, bail, Error};
4142
4243pub use helix_core::diagnostic::Severity;
43−pub use helix_core::register::Registers;
4444use helix_core::{
4545 auto_pairs::AutoPairs,
4646 syntax::{self, AutoPairConfig, SoftWrap},
helix-view/src/info.rs+4−11
@@ -1,4 +1,5 @@
1−use helix_core::{register::Registers, unicode::width::UnicodeWidthStr};
1+use crate::register::Registers;
2+use helix_core::unicode::width::UnicodeWidthStr;
23use std::fmt::Write;
34
45#[derive(Debug)]
@@ -56,16 +57,8 @@ impl Info {
5657
5758 pub fn from_registers(registers: &Registers) -> Self {
5859 let body: Vec<_> = registers
59− .inner()
60− .iter()
61− .map(|(ch, reg)| {
62− let content = reg
63− .read()
64− .get(0)
65− .and_then(|s| s.lines().next())
66− .unwrap_or_default();
67− (ch.to_string(), content)
68− })
60+ .iter_preview()
61+ .map(|(ch, preview)| (ch.to_string(), preview))
6962 .collect();
7063
7164 let mut infobox = Self::new("Registers", &body);