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.
main
Michael Davis 10 months ago committed by Blaž Hrastnik
parent 0f19f282cf
commit baceb02a09

@ -18,7 +18,6 @@ pub mod movement;
pub mod object; pub mod object;
pub mod path; pub mod path;
mod position; mod position;
pub mod register;
pub mod search; pub mod search;
pub mod selection; pub mod selection;
pub mod shellwords; pub mod shellwords;

@ -1,89 +0,0 @@
use std::collections::HashMap;
#[derive(Debug)]
pub struct Register {
name: char,
values: Vec<String>,
}
impl Register {
pub const fn new(name: char) -> Self {
Self {
name,
values: Vec::new(),
}
}
pub fn new_with_values(name: char, values: Vec<String>) -> Self {
Self { name, values }
}
pub const fn name(&self) -> char {
self.name
}
pub fn read(&self) -> &[String] {
&self.values
}
pub fn write(&mut self, values: Vec<String>) {
self.values = values;
}
pub fn push(&mut self, value: String) {
self.values.push(value);
}
}
/// Currently just wraps a `HashMap` of `Register`s
#[derive(Debug, Default)]
pub struct Registers {
inner: HashMap<char, Register>,
}
impl Registers {
pub fn get(&self, name: char) -> Option<&Register> {
self.inner.get(&name)
}
pub fn read(&self, name: char) -> Option<&[String]> {
self.get(name).map(|reg| reg.read())
}
pub fn write(&mut self, name: char, values: Vec<String>) {
if name != '_' {
self.inner
.insert(name, Register::new_with_values(name, values));
}
}
pub fn push(&mut self, name: char, value: String) {
if name != '_' {
if let Some(r) = self.inner.get_mut(&name) {
r.push(value);
} else {
self.write(name, vec![value]);
}
}
}
pub fn first(&self, name: char) -> Option<&String> {
self.read(name).and_then(|entries| entries.first())
}
pub fn last(&self, name: char) -> Option<&String> {
self.read(name).and_then(|entries| entries.last())
}
pub fn inner(&self) -> &HashMap<char, Register> {
&self.inner
}
pub fn clear(&mut self) {
self.inner.clear();
}
pub fn remove(&mut self, name: char) -> Option<Register> {
self.inner.remove(&name)
}
}

@ -1847,11 +1847,11 @@ fn search_impl(
fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> { fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
let mut items = reg let mut items = reg
.and_then(|reg| cx.editor.registers.get(reg)) .and_then(|reg| cx.editor.registers.read(reg, cx.editor))
.map_or(Vec::new(), |reg| reg.read().iter().take(200).collect()); .map_or(Vec::new(), |reg| reg.take(200).collect());
items.sort_unstable(); items.sort_unstable();
items.dedup(); items.dedup();
items.into_iter().cloned().collect() items.into_iter().map(|value| value.to_string()).collect()
} }
fn search(cx: &mut Context) { fn search(cx: &mut Context) {
@ -1910,9 +1910,8 @@ fn search_next_or_prev_impl(cx: &mut Context, movement: Movement, direction: Dir
let count = cx.count(); let count = cx.count();
let config = cx.editor.config(); let config = cx.editor.config();
let scrolloff = config.scrolloff; let scrolloff = config.scrolloff;
let (_, doc) = current!(cx.editor); if let Some(query) = cx.editor.registers.last('/', cx.editor) {
let registers = &cx.editor.registers; let doc = doc!(cx.editor);
if let Some(query) = registers.read('/').and_then(|query| query.last()) {
let contents = doc.text().slice(..).to_string(); let contents = doc.text().slice(..).to_string();
let search_config = &config.search; let search_config = &config.search;
let case_insensitive = if search_config.smart_case { 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
false false
}; };
let wrap_around = search_config.wrap_around; let wrap_around = search_config.wrap_around;
if let Ok(regex) = RegexBuilder::new(query) if let Ok(regex) = RegexBuilder::new(&query)
.case_insensitive(case_insensitive) .case_insensitive(case_insensitive)
.multi_line(true) .multi_line(true)
.build() .build()
@ -1974,12 +1973,14 @@ fn search_selection(cx: &mut Context) {
.join("|"); .join("|");
let msg = format!("register '{}' set to '{}'", '/', &regex); let msg = format!("register '{}' set to '{}'", '/', &regex);
cx.editor.registers.push('/', regex); match cx.editor.registers.push('/', regex) {
cx.editor.set_status(msg); Ok(_) => cx.editor.set_status(msg),
Err(err) => cx.editor.set_error(err.to_string()),
}
} }
fn make_search_word_bounded(cx: &mut Context) { fn make_search_word_bounded(cx: &mut Context) {
let regex = match cx.editor.registers.last('/') { let regex = match cx.editor.registers.last('/', cx.editor) {
Some(regex) => regex, Some(regex) => regex,
None => return, None => return,
}; };
@ -1997,14 +1998,16 @@ fn make_search_word_bounded(cx: &mut Context) {
if !start_anchored { if !start_anchored {
new_regex.push_str("\\b"); new_regex.push_str("\\b");
} }
new_regex.push_str(regex); new_regex.push_str(&regex);
if !end_anchored { if !end_anchored {
new_regex.push_str("\\b"); new_regex.push_str("\\b");
} }
let msg = format!("register '{}' set to '{}'", '/', &new_regex); let msg = format!("register '{}' set to '{}'", '/', &new_regex);
cx.editor.registers.push('/', new_regex); match cx.editor.registers.push('/', new_regex) {
cx.editor.set_status(msg); Ok(_) => cx.editor.set_status(msg),
Err(err) => cx.editor.set_error(err.to_string()),
}
} }
fn global_search(cx: &mut Context) { fn global_search(cx: &mut Context) {
@ -2367,7 +2370,10 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) {
let text = doc.text().slice(..); let text = doc.text().slice(..);
let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect(); let values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
let reg_name = cx.register.unwrap_or('"'); let reg_name = cx.register.unwrap_or('"');
cx.editor.registers.write(reg_name, values); if let Err(err) = cx.editor.registers.write(reg_name, values) {
cx.editor.set_error(err.to_string());
return;
}
}; };
// then delete // then delete
@ -3758,18 +3764,16 @@ fn yank(cx: &mut Context) {
.fragments(text) .fragments(text)
.map(Cow::into_owned) .map(Cow::into_owned)
.collect(); .collect();
let selections = values.len();
let register = cx.register.unwrap_or('"');
let msg = format!( match cx.editor.registers.write(register, values) {
"yanked {} selection(s) to register {}", Ok(_) => cx.editor.set_status(format!(
values.len(), "yanked {selections} selection(s) to register {register}",
cx.register.unwrap_or('"') )),
); Err(err) => cx.editor.set_error(err.to_string()),
}
cx.editor
.registers
.write(cx.register.unwrap_or('"'), values);
cx.editor.set_status(msg);
exit_select_mode(cx); exit_select_mode(cx);
} }
@ -3778,6 +3782,7 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
let text = doc.text().slice(..); let text = doc.text().slice(..);
let selection = doc.selection(view.id); let selection = doc.selection(view.id);
let selections = selection.len();
let joined = selection let joined = selection
.fragments(text) .fragments(text)
.fold(String::new(), |mut acc, fragment| { .fold(String::new(), |mut acc, fragment| {
@ -3788,14 +3793,12 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
acc acc
}); });
let msg = format!( match editor.registers.write(register, vec![joined]) {
"joined and yanked {} selection(s) to register {}", Ok(_) => editor.set_status(format!(
selection.len(), "joined and yanked {selections} selection(s) to register {register}",
register, )),
); Err(err) => editor.set_error(err.to_string()),
}
editor.registers.write(register, vec![joined]);
editor.set_status(msg);
} }
fn yank_joined(cx: &mut Context) { fn yank_joined(cx: &mut Context) {
@ -4040,34 +4043,34 @@ fn paste_primary_clipboard_before(cx: &mut Context) {
fn replace_with_yanked(cx: &mut Context) { fn replace_with_yanked(cx: &mut Context) {
let count = cx.count(); let count = cx.count();
let reg_name = cx.register.unwrap_or('"'); let reg_name = cx.register.unwrap_or('"');
let (view, doc) = current!(cx.editor);
let registers = &mut cx.editor.registers;
if let Some(values) = registers.read(reg_name) {
if !values.is_empty() {
let repeat = std::iter::repeat(
values
.last()
.map(|value| Tendril::from(&value.repeat(count)))
.unwrap(),
);
let mut values = values
.iter()
.map(|value| Tendril::from(&value.repeat(count)))
.chain(repeat);
let selection = doc.selection(view.id);
let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
if !range.is_empty() {
(range.from(), range.to(), Some(values.next().unwrap()))
} else {
(range.from(), range.to(), None)
}
});
doc.apply(&transaction, view.id); let Some(values) = cx.editor.registers
exit_select_mode(cx); .read(reg_name, cx.editor)
.filter(|values| values.len() > 0) else { return };
let values: Vec<_> = values.map(|value| value.to_string()).collect();
let (view, doc) = current!(cx.editor);
let repeat = std::iter::repeat(
values
.last()
.map(|value| Tendril::from(&value.repeat(count)))
.unwrap(),
);
let mut values = values
.iter()
.map(|value| Tendril::from(&value.repeat(count)))
.chain(repeat);
let selection = doc.selection(view.id);
let transaction = Transaction::change_by_selection(doc.text(), selection, |range| {
if !range.is_empty() {
(range.from(), range.to(), Some(values.next().unwrap()))
} else {
(range.from(), range.to(), None)
} }
} });
doc.apply(&transaction, view.id);
exit_select_mode(cx);
} }
fn replace_selections_with_clipboard_impl( fn replace_selections_with_clipboard_impl(
@ -4109,12 +4112,12 @@ fn replace_selections_with_primary_clipboard(cx: &mut Context) {
fn paste(cx: &mut Context, pos: Paste) { fn paste(cx: &mut Context, pos: Paste) {
let count = cx.count(); let count = cx.count();
let reg_name = cx.register.unwrap_or('"'); let reg_name = cx.register.unwrap_or('"');
let (view, doc) = current!(cx.editor);
let registers = &mut cx.editor.registers;
if let Some(values) = registers.read(reg_name) { let Some(values) = cx.editor.registers.read(reg_name, cx.editor) else { return };
paste_impl(values, doc, view, pos, count, cx.editor.mode); let values: Vec<_> = values.map(|value| value.to_string()).collect();
}
let (view, doc) = current!(cx.editor);
paste_impl(&values, doc, view, pos, count, cx.editor.mode);
} }
fn paste_after(cx: &mut Context) { fn paste_after(cx: &mut Context) {
@ -5593,9 +5596,12 @@ fn record_macro(cx: &mut Context) {
} }
}) })
.collect::<String>(); .collect::<String>();
cx.editor.registers.write(reg, vec![s]); match cx.editor.registers.write(reg, vec![s]) {
cx.editor Ok(_) => cx
.set_status(format!("Recorded to register [{}]", reg)); .editor
.set_status(format!("Recorded to register [{}]", reg)),
Err(err) => cx.editor.set_error(err.to_string()),
}
} else { } else {
let reg = cx.register.take().unwrap_or('@'); let reg = cx.register.take().unwrap_or('@');
cx.editor.macro_recording = Some((reg, Vec::new())); cx.editor.macro_recording = Some((reg, Vec::new()));
@ -5615,8 +5621,14 @@ fn replay_macro(cx: &mut Context) {
return; return;
} }
let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) { let keys: Vec<KeyEvent> = if let Some(keys) = cx
match helix_view::input::parse_macro(keys_str) { .editor
.registers
.read(reg, cx.editor)
.filter(|values| values.len() == 1)
.map(|mut values| values.next().unwrap())
{
match helix_view::input::parse_macro(&keys) {
Ok(keys) => keys, Ok(keys) => keys,
Err(err) => { Err(err) => {
cx.editor.set_error(format!("Invalid macro: {}", err)); cx.editor.set_error(format!("Invalid macro: {}", err));

@ -2285,13 +2285,12 @@ fn clear_register(
format!("Invalid register {}", args[0]) format!("Invalid register {}", args[0])
); );
let register = args[0].chars().next().unwrap_or_default(); let register = args[0].chars().next().unwrap_or_default();
match cx.editor.registers.remove(register) { if cx.editor.registers.remove(register) {
Some(_) => cx cx.editor
.editor .set_status(format!("Register {} cleared", register));
.set_status(format!("Register {} cleared", register)), } else {
None => cx cx.editor
.editor .set_error(format!("Register {} not found", register));
.set_error(format!("Register {} not found", register)),
} }
Ok(()) Ok(())
} }

@ -306,8 +306,8 @@ impl Prompt {
direction: CompletionDirection, direction: CompletionDirection,
) { ) {
(self.callback_fn)(cx, &self.line, PromptEvent::Abort); (self.callback_fn)(cx, &self.line, PromptEvent::Abort);
let values = match cx.editor.registers.read(register) { let mut values = match cx.editor.registers.read(register, cx.editor) {
Some(values) if !values.is_empty() => values, Some(values) if values.len() > 0 => values,
_ => return, _ => return,
}; };
@ -315,13 +315,16 @@ impl Prompt {
let index = match direction { let index = match direction {
CompletionDirection::Forward => self.history_pos.map_or(0, |i| i + 1), CompletionDirection::Forward => self.history_pos.map_or(0, |i| i + 1),
CompletionDirection::Backward => { CompletionDirection::Backward => self
self.history_pos.unwrap_or(values.len()).saturating_sub(1) .history_pos
} .unwrap_or_else(|| values.len())
.saturating_sub(1),
} }
.min(end); .min(end);
self.line = values[index].clone(); self.line = values.nth(index).unwrap().to_string();
// Appease the borrow checker.
drop(values);
self.history_pos = Some(index); self.history_pos = Some(index);
@ -470,7 +473,7 @@ impl Prompt {
// Show the most recently entered value as a suggestion. // Show the most recently entered value as a suggestion.
if let Some(suggestion) = self if let Some(suggestion) = self
.history_register .history_register
.and_then(|reg| cx.editor.registers.last(reg)) .and_then(|reg| cx.editor.registers.last(reg, cx.editor))
{ {
surface.set_string(line_area.x, line_area.y, suggestion, suggestion_color); surface.set_string(line_area.x, line_area.y, suggestion, suggestion_color);
} }
@ -567,25 +570,29 @@ impl Component for Prompt {
} else { } else {
let last_item = self let last_item = self
.history_register .history_register
.and_then(|reg| cx.editor.registers.last(reg).cloned()) .and_then(|reg| cx.editor.registers.last(reg, cx.editor))
.map(|entry| entry.into()) .map(|entry| entry.to_string())
.unwrap_or_else(|| Cow::from("")); .unwrap_or_else(|| String::from(""));
// handle executing with last command in history if nothing entered // handle executing with last command in history if nothing entered
let input: Cow<str> = if self.line.is_empty() { let input = if self.line.is_empty() {
last_item &last_item
} else { } else {
if last_item != self.line { if last_item != self.line {
// store in history // store in history
if let Some(register) = self.history_register { if let Some(register) = self.history_register {
cx.editor.registers.push(register, self.line.clone()); if let Err(err) =
cx.editor.registers.push(register, self.line.clone())
{
cx.editor.set_error(err.to_string());
}
}; };
} }
self.line.as_str().into() &self.line
}; };
(self.callback_fn)(cx, &input, PromptEvent::Validate); (self.callback_fn)(cx, input, PromptEvent::Validate);
return close_fn; return close_fn;
} }
@ -617,25 +624,16 @@ impl Component for Prompt {
self.completion = cx self.completion = cx
.editor .editor
.registers .registers
.inner() .iter_preview()
.iter() .map(|(ch, preview)| (0.., format!("{} {}", ch, &preview).into()))
.map(|(ch, reg)| {
let content = reg
.read()
.get(0)
.and_then(|s| s.lines().next().to_owned())
.unwrap_or_default();
(0.., format!("{} {}", ch, &content).into())
})
.collect(); .collect();
self.next_char_handler = Some(Box::new(|prompt, c, context| { self.next_char_handler = Some(Box::new(|prompt, c, context| {
prompt.insert_str( prompt.insert_str(
context &context
.editor .editor
.registers .registers
.read(c) .first(c, context.editor)
.and_then(|r| r.first()) .unwrap_or_default(),
.map_or("", |r| r.as_str()),
context.editor, context.editor,
); );
})); }));

@ -5,6 +5,7 @@ use crate::{
graphics::{CursorKind, Rect}, graphics::{CursorKind, Rect},
info::Info, info::Info,
input::KeyEvent, input::KeyEvent,
register::Registers,
theme::{self, Theme}, theme::{self, Theme},
tree::{self, Tree}, tree::{self, Tree},
view::ViewPosition, view::ViewPosition,
@ -40,7 +41,6 @@ use tokio::{
use anyhow::{anyhow, bail, Error}; use anyhow::{anyhow, bail, Error};
pub use helix_core::diagnostic::Severity; pub use helix_core::diagnostic::Severity;
pub use helix_core::register::Registers;
use helix_core::{ use helix_core::{
auto_pairs::AutoPairs, auto_pairs::AutoPairs,
syntax::{self, AutoPairConfig, SoftWrap}, syntax::{self, AutoPairConfig, SoftWrap},

@ -1,4 +1,5 @@
use helix_core::{register::Registers, unicode::width::UnicodeWidthStr}; use crate::register::Registers;
use helix_core::unicode::width::UnicodeWidthStr;
use std::fmt::Write; use std::fmt::Write;
#[derive(Debug)] #[derive(Debug)]
@ -56,16 +57,8 @@ impl Info {
pub fn from_registers(registers: &Registers) -> Self { pub fn from_registers(registers: &Registers) -> Self {
let body: Vec<_> = registers let body: Vec<_> = registers
.inner() .iter_preview()
.iter() .map(|(ch, preview)| (ch.to_string(), preview))
.map(|(ch, reg)| {
let content = reg
.read()
.get(0)
.and_then(|s| s.lines().next())
.unwrap_or_default();
(ch.to_string(), content)
})
.collect(); .collect();
let mut infobox = Self::new("Registers", &body); let mut infobox = Self::new("Registers", &body);

Loading…
Cancel
Save