on master help sign in
Use refactored Registers type
baceb02Michael Davis
helix-core/src/lib.rs−1
@@ -18,7 +18,6 @@ pub mod movement;
18pub mod object;
18pub mod object;
19pub mod path;
19pub mod path;
20mod position;
20mod position;
21−pub mod register;
22pub mod search;
21pub mod search;
23pub mod selection;
22pub mod selection;
24pub mod shellwords;
23pub 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(
1847
1847
1848fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
1848fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
1849 let mut items = reg
1849 let mut items = reg
1850− .and_then(|reg| cx.editor.registers.get(reg))
1850+ .and_then(|reg| cx.editor.registers.read(reg, cx.editor))
1851− .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect());
1851+ .map_or(Vec::new(), |reg| reg.take(200).collect());
1852 items.sort_unstable();
1852 items.sort_unstable();
1853 items.dedup();
1853 items.dedup();
1854− items.into_iter().cloned().collect()
1854+ items.into_iter().map(|value| value.to_string()).collect()
1855}
1855}
1856
1856
1857fn search(cx: &mut Context) {
1857fn search(cx: &mut Context) {
@@ -1910,9 +1910,8 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
1910 let count = cx.count();
1910 let count = cx.count();
1911 let config = cx.editor.config();
1911 let config = cx.editor.config();
1912 let scrolloff = config.scrolloff;
1912 let scrolloff = config.scrolloff;
1913− let (_, doc) = current!(cx.editor);
1913+ if let Some(query) = cx.editor.registers.last('/', cx.editor) {
1914− let registers = &cx.editor.registers;
1914+ let doc = doc!(cx.editor);
1915− if let Some(query) = registers.read('/').and_then(|query| query.last()) {
1916 let contents = doc.text().slice(..).to_string();
1915 let contents = doc.text().slice(..).to_string();
1917 let search_config = &config.search;
1916 let search_config = &config.search;
1918 let case_insensitive = if search_config.smart_case {
1917 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
1921 false
1920 false
1922 };
1921 };
1923 let wrap_around = search_config.wrap_around;
1922 let wrap_around = search_config.wrap_around;
1924− if let Ok(regex) = RegexBuilder::new(query)
1923+ if let Ok(regex) = RegexBuilder::new(&query)
1925 .case_insensitive(case_insensitive)
1924 .case_insensitive(case_insensitive)
1926 .multi_line(true)
1925 .multi_line(true)
1927 .build()
1926 .build()
@@ -1974,12 +1973,14 @@ fn search_selection(cx: &mut Context) {
1974 .join("|");
1973 .join("|");
1975
1974
1976 let msg = format!("register '{}' set to '{}'", '/', &regex);
1975 let msg = format!("register '{}' set to '{}'", '/', &regex);
1977− cx.editor.registers.push('/', regex);
1976+ match cx.editor.registers.push('/', regex) {
1978− cx.editor.set_status(msg);
1977+ Ok(_) => cx.editor.set_status(msg),
1978+ Err(err) => cx.editor.set_error(err.to_string()),
1979+ }
1979}
1980}
1980
1981
1981fn make_search_word_bounded(cx: &mut Context) {
1982fn 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) {
1983 Some(regex) => regex,
1984 Some(regex) => regex,
1984 None => return,
1985 None => return,
1985 };
1986 };
@@ -1997,14 +1998,16 @@ fn make_search_word_bounded(cx: &mut Context) {
1997 if !start_anchored {
1998 if !start_anchored {
1998 new_regex.push_str("\\b");
1999 new_regex.push_str("\\b");
1999 }
2000 }
2000− new_regex.push_str(regex);
2001+ new_regex.push_str(&regex);
2001 if !end_anchored {
2002 if !end_anchored {
2002 new_regex.push_str("\\b");
2003 new_regex.push_str("\\b");
2003 }
2004 }
2004
2005
2005 let msg = format!("register '{}' set to '{}'", '/', &new_regex);
2006 let msg = format!("register '{}' set to '{}'", '/', &new_regex);
2006− cx.editor.registers.push('/', new_regex);
2007+ match cx.editor.registers.push('/', new_regex) {
2007− cx.editor.set_status(msg);
2008+ Ok(_) => cx.editor.set_status(msg),
2009+ Err(err) => cx.editor.set_error(err.to_string()),
2010+ }
2008}
2011}
2009
2012
2010fn global_search(cx: &mut Context) {
2013fn global_search(cx: &mut Context) {
@@ -2367,7 +2370,10 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
2367 let text = doc.text().slice(..);
2370 let text = doc.text().slice(..);
2368 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
2371 let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
2369 let reg_name = cx.register.unwrap_or('"');
2372 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+ }
2371 };
2377 };
2372
2378
2373 // then delete
2379 // then delete
@@ -3758,18 +3764,16 @@ fn yank(cx: &mut Context) {
3758 .fragments(text)
3764 .fragments(text)
3759 .map(Cow::into_owned)
3765 .map(Cow::into_owned)
3760 .collect();
3766 .collect();
3767+ let selections = values.len();
3768+ let register = cx.register.unwrap_or('"');
3761
3769
3762− let msg = format!(
3770+ match cx.editor.registers.write(register, values) {
3771+ Ok(_) => cx.editor.set_status(format!(
3763− "yanked {} selection(s) to register {}",
3772+ "yanked {selections} selection(s) to register {register}",
3764− values.len(),
3773+ )),
3765− cx.register.unwrap_or('"')
3774+ Err(err) => cx.editor.set_error(err.to_string()),
3766− );
3775+ }
3767−
3768− cx.editor
3769− .registers
3770− .write(cx.register.unwrap_or('"'), values);
3771
3776
3772− cx.editor.set_status(msg);
3773 exit_select_mode(cx);
3777 exit_select_mode(cx);
3774}
3778}
3775
3779
@@ -3778,6 +3782,7 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
3778 let text = doc.text().slice(..);
3782 let text = doc.text().slice(..);
3779
3783
3780 let selection = doc.selection(view.id);
3784 let selection = doc.selection(view.id);
3785+ let selections = selection.len();
3781 let joined = selection
3786 let joined = selection
3782 .fragments(text)
3787 .fragments(text)
3783 .fold(String::new(), |mut acc, fragment| {
3788 .fold(String::new(), |mut acc, fragment| {
@@ -3788,14 +3793,12 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
3788 acc
3793 acc
3789 });
3794 });
3790
3795
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]);
3796+ match editor.registers.write(register, vec![joined]) {
3798− editor.set_status(msg);
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+ }
3799}
3802}
3800
3803
3801fn yank_joined(cx: &mut Context) {
3804fn yank_joined(cx: &mut Context) {
@@ -4040,34 +4043,34 @@ fn paste_primary_clipboard_before(cx: &mut Context) {
4040fn replace_with_yanked(cx: &mut Context) {
4043fn replace_with_yanked(cx: &mut Context) {
4041 let count = cx.count();
4044 let count = cx.count();
4042 let reg_name = cx.register.unwrap_or('"');
4045 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− });
4066
4046
4067− doc.apply(&transaction, view.id);
4047+ let Some(values) = cx.editor.registers
4068− exit_select_mode(cx);
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)
4069 }
4069 }
4070− }
4070+ });
4071+
4072+ doc.apply(&transaction, view.id);
4073+ exit_select_mode(cx);
4071}
4074}
4072
4075
4073fn replace_selections_with_clipboard_impl(
4076fn replace_selections_with_clipboard_impl(
@@ -4109,12 +4112,12 @@ fn replace_selections_with_primary_clipboard(cx: &mut Context) {
4109fn paste(cx: &mut Context, pos: Paste) {
4112fn paste(cx: &mut Context, pos: Paste) {
4110 let count = cx.count();
4113 let count = cx.count();
4111 let reg_name = cx.register.unwrap_or('"');
4114 let reg_name = cx.register.unwrap_or('"');
4112− let (view, doc) = current!(cx.editor);
4113− let registers = &mut cx.editor.registers;
4114
4115
4115− if let Some(values) = registers.read(reg_name) {
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);
4116− paste_impl(values, doc, view, pos, count, cx.editor.mode);
4120+ paste_impl(&values, doc, view, pos, count, cx.editor.mode);
4117− }
4118}
4121}
4119
4122
4120fn paste_after(cx: &mut Context) {
4123fn paste_after(cx: &mut Context) {
@@ -5593,9 +5596,12 @@ fn record_macro(cx: &mut Context) {
5593 }
5596 }
5594 })
5597 })
5595 .collect::<String>();
5598 .collect::<String>();
5596− cx.editor.registers.write(reg, vec![s]);
5599+ match cx.editor.registers.write(reg, vec![s]) {
5600+ Ok(_) => cx
5597− cx.editor
5601+ .editor
5598− .set_status(format!("Recorded to register [{}]", reg));
5602+ .set_status(format!("Recorded to register [{}]", reg)),
5603+ Err(err) => cx.editor.set_error(err.to_string()),
5604+ }
5599 } else {
5605 } else {
5600 let reg = cx.register.take().unwrap_or('@');
5606 let reg = cx.register.take().unwrap_or('@');
5601 cx.editor.macro_recording = Some((reg, Vec::new()));
5607 cx.editor.macro_recording = Some((reg, Vec::new()));
@@ -5615,8 +5621,14 @@ fn replay_macro(cx: &mut Context) {
5615 return;
5621 return;
5616 }
5622 }
5617
5623
5618− let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
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+ {
5619− match helix_view::input::parse_macro(keys_str) {
5631+ match helix_view::input::parse_macro(&keys) {
5620 Ok(keys) => keys,
5632 Ok(keys) => keys,
5621 Err(err) => {
5633 Err(err) => {
5622 cx.editor.set_error(format!("Invalid macro: {}", err));
5634 cx.editor.set_error(format!("Invalid macro: {}", err));
helix-term/src/commands/typed.rs+6−7
@@ -2285,13 +2285,12 @@ fn clear_register(
2285 format!("Invalid register {}", args[0])
2285 format!("Invalid register {}", args[0])
2286 );
2286 );
2287 let register = args[0].chars().next().unwrap_or_default();
2287 let register = args[0].chars().next().unwrap_or_default();
2288− match cx.editor.registers.remove(register) {
2288+ if cx.editor.registers.remove(register) {
2289− Some(_) => cx
2290− .editor
2289+ cx.editor
2291− .set_status(format!("Register {} cleared", register)),
2290+ .set_status(format!("Register {} cleared", register));
2292− None => cx
2291+ } else {
2293− .editor
2292+ cx.editor
2294− .set_error(format!("Register {} not found", register)),
2293+ .set_error(format!("Register {} not found", register));
2295 }
2294 }
2296 Ok(())
2295 Ok(())
2297}
2296}
helix-term/src/ui/prompt.rs+27−29
@@ -306,8 +306,8 @@ impl Prompt {
306 direction: CompletionDirection,
306 direction: CompletionDirection,
307 ) {
307 ) {
308 (self.callback_fn)(cx, &self.line, PromptEvent::Abort);
308 (self.callback_fn)(cx, &self.line, PromptEvent::Abort);
309− let values = match cx.editor.registers.read(register) {
309+ let mut values = match cx.editor.registers.read(register, cx.editor) {
310− Some(values) if !values.is_empty() => values,
310+ Some(values) if values.len() > 0 => values,
311 _ => return,
311 _ => return,
312 };
312 };
313
313
@@ -315,13 +315,16 @@ impl Prompt {
315
315
316 let index = match direction {
316 let index = match direction {
317 CompletionDirection::Forward => self.history_pos.map_or(0, |i| i + 1),
317 CompletionDirection::Forward => self.history_pos.map_or(0, |i| i + 1),
318− CompletionDirection::Backward => {
318+ CompletionDirection::Backward => self
319+ .history_pos
320+ .unwrap_or_else(|| values.len())
319− self.history_pos.unwrap_or(values.len()).saturating_sub(1)
321+ .saturating_sub(1),
320− }
321 }
322 }
322 .min(end);
323 .min(end);
323
324
324− self.line = values[index].clone();
325+ self.line = values.nth(index).unwrap().to_string();
326+ // Appease the borrow checker.
327+ drop(values);
325
328
326 self.history_pos = Some(index);
329 self.history_pos = Some(index);
327
330
@@ -470,7 +473,7 @@ impl Prompt {
470 // Show the most recently entered value as a suggestion.
473 // Show the most recently entered value as a suggestion.
471 if let Some(suggestion) = self
474 if let Some(suggestion) = self
472 .history_register
475 .history_register
473− .and_then(|reg| cx.editor.registers.last(reg))
476+ .and_then(|reg| cx.editor.registers.last(reg, cx.editor))
474 {
477 {
475 surface.set_string(line_area.x, line_area.y, suggestion, suggestion_color);
478 surface.set_string(line_area.x, line_area.y, suggestion, suggestion_color);
476 }
479 }
@@ -567,25 +570,29 @@ impl Component for Prompt {
567 } else {
570 } else {
568 let last_item = self
571 let last_item = self
569 .history_register
572 .history_register
570− .and_then(|reg| cx.editor.registers.last(reg).cloned())
573+ .and_then(|reg| cx.editor.registers.last(reg, cx.editor))
571− .map(|entry| entry.into())
574+ .map(|entry| entry.to_string())
572− .unwrap_or_else(|| Cow::from(""));
575+ .unwrap_or_else(|| String::from(""));
573
576
574 // handle executing with last command in history if nothing entered
577 // handle executing with last command in history if nothing entered
575− let input: Cow<str> = if self.line.is_empty() {
578+ let input = if self.line.is_empty() {
576− last_item
579+ &last_item
577 } else {
580 } else {
578 if last_item != self.line {
581 if last_item != self.line {
579 // store in history
582 // store in history
580 if let Some(register) = self.history_register {
583 if let Some(register) = self.history_register {
584+ if let Err(err) =
581− cx.editor.registers.push(register, self.line.clone());
585+ cx.editor.registers.push(register, self.line.clone())
586+ {
587+ cx.editor.set_error(err.to_string());
588+ }
582 };
589 };
583 }
590 }
584
591
585− self.line.as_str().into()
592+ &self.line
586 };
593 };
587
594
588− (self.callback_fn)(cx, &input, PromptEvent::Validate);
595+ (self.callback_fn)(cx, input, PromptEvent::Validate);
589
596
590 return close_fn;
597 return close_fn;
591 }
598 }
@@ -617,25 +624,16 @@ impl Component for Prompt {
617 self.completion = cx
624 self.completion = cx
618 .editor
625 .editor
619 .registers
626 .registers
620− .inner()
627+ .iter_preview()
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())
628+ .map(|(ch, preview)| (0.., format!("{} {}", ch, &preview).into()))
629− })
630 .collect();
629 .collect();
631 self.next_char_handler = Some(Box::new(|prompt, c, context| {
630 self.next_char_handler = Some(Box::new(|prompt, c, context| {
632 prompt.insert_str(
631 prompt.insert_str(
633− context
632+ &context
634 .editor
633 .editor
635 .registers
634 .registers
636− .read(c)
635+ .first(c, context.editor)
637− .and_then(|r| r.first())
636+ .unwrap_or_default(),
638− .map_or("", |r| r.as_str()),
639 context.editor,
637 context.editor,
640 );
638 );
641 }));
639 }));
helix-view/src/editor.rs+1−1
@@ -5,6 +5,7 @@ use crate::{
5 graphics::{CursorKind, Rect},
5 graphics::{CursorKind, Rect},
6 info::Info,
6 info::Info,
7 input::KeyEvent,
7 input::KeyEvent,
8+ register::Registers,
8 theme::{self, Theme},
9 theme::{self, Theme},
9 tree::{self, Tree},
10 tree::{self, Tree},
10 view::ViewPosition,
11 view::ViewPosition,
@@ -40,7 +41,6 @@ use tokio::{
40use anyhow::{anyhow, bail, Error};
41use anyhow::{anyhow, bail, Error};
41
42
42pub use helix_core::diagnostic::Severity;
43pub use helix_core::diagnostic::Severity;
43−pub use helix_core::register::Registers;
44use helix_core::{
44use helix_core::{
45 auto_pairs::AutoPairs,
45 auto_pairs::AutoPairs,
46 syntax::{self, AutoPairConfig, SoftWrap},
46 syntax::{self, AutoPairConfig, SoftWrap},
helix-view/src/info.rs+4−11
@@ -1,4 +1,5 @@
1+use crate::register::Registers;
1−use helix_core::{register::Registers, unicode::width::UnicodeWidthStr};
2+use helix_core::unicode::width::UnicodeWidthStr;
2use std::fmt::Write;
3use std::fmt::Write;
3
4
4#[derive(Debug)]
5#[derive(Debug)]
@@ -56,16 +57,8 @@ impl Info {
56
57
57 pub fn from_registers(registers: &Registers) -> Self {
58 pub fn from_registers(registers: &Registers) -> Self {
58 let body: Vec<_> = registers
59 let body: Vec<_> = registers
59− .inner()
60+ .iter_preview()
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)
61+ .map(|(ch, preview)| (ch.to_string(), preview))
68− })
69 .collect();
62 .collect();
70
63
71 let mut infobox = Self::new("Registers", &body);
64 let mut infobox = Self::new("Registers", &body);