aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-core/src/lib.rs1
-rw-r--r--helix-core/src/register.rs89
-rw-r--r--helix-term/src/commands.rs148
-rw-r--r--helix-term/src/commands/typed.rs13
-rw-r--r--helix-term/src/ui/prompt.rs56
-rw-r--r--helix-view/src/editor.rs2
-rw-r--r--helix-view/src/info.rs15
7 files changed, 118 insertions, 206 deletions
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index e1b5a1a1..9a512eae 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -18,7 +18,6 @@ pub mod movement;
pub mod object;
pub mod path;
mod position;
-pub mod register;
pub mod search;
pub mod selection;
pub mod shellwords;
diff --git a/helix-core/src/register.rs b/helix-core/src/register.rs
deleted file mode 100644
index df68a759..00000000
--- a/helix-core/src/register.rs
+++ /dev/null
@@ -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)
- }
-}
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 58c17296..d9ea580d 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -1847,11 +1847,11 @@ fn search_impl(
fn search_completions(cx: &mut Context, reg: Option<char>) -> Vec<String> {
let mut items = reg
- .and_then(|reg| cx.editor.registers.get(reg))
- .map_or(Vec::new(), |reg| reg.read().iter().take(200).collect());
+ .and_then(|reg| cx.editor.registers.read(reg, cx.editor))
+ .map_or(Vec::new(), |reg| reg.take(200).collect());
items.sort_unstable();
items.dedup();
- items.into_iter().cloned().collect()
+ items.into_iter().map(|value| value.to_string()).collect()
}
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 config = cx.editor.config();
let scrolloff = config.scrolloff;
- let (_, doc) = current!(cx.editor);
- let registers = &cx.editor.registers;
- if let Some(query) = registers.read('/').and_then(|query| query.last()) {
+ if let Some(query) = cx.editor.registers.last('/', cx.editor) {
+ let doc = doc!(cx.editor);
let contents = doc.text().slice(..).to_string();
let search_config = &config.search;
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
};
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)
.multi_line(true)
.build()
@@ -1974,12 +1973,14 @@ fn search_selection(cx: &mut Context) {
.join("|");
let msg = format!("register '{}' set to '{}'", '/', &regex);
- cx.editor.registers.push('/', regex);
- cx.editor.set_status(msg);
+ match cx.editor.registers.push('/', regex) {
+ Ok(_) => cx.editor.set_status(msg),
+ Err(err) => cx.editor.set_error(err.to_string()),
+ }
}
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,
None => return,
};
@@ -1997,14 +1998,16 @@ fn make_search_word_bounded(cx: &mut Context) {
if !start_anchored {
new_regex.push_str("\\b");
}
- new_regex.push_str(regex);
+ new_regex.push_str(&regex);
if !end_anchored {
new_regex.push_str("\\b");
}
let msg = format!("register '{}' set to '{}'", '/', &new_regex);
- cx.editor.registers.push('/', new_regex);
- cx.editor.set_status(msg);
+ match cx.editor.registers.push('/', new_regex) {
+ Ok(_) => cx.editor.set_status(msg),
+ Err(err) => cx.editor.set_error(err.to_string()),
+ }
}
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 values: Vec<String> = selection.fragments(text).map(Cow::into_owned).collect();
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
@@ -3758,18 +3764,16 @@ fn yank(cx: &mut Context) {
.fragments(text)
.map(Cow::into_owned)
.collect();
+ let selections = values.len();
+ let register = cx.register.unwrap_or('"');
- let msg = format!(
- "yanked {} selection(s) to register {}",
- values.len(),
- cx.register.unwrap_or('"')
- );
-
- cx.editor
- .registers
- .write(cx.register.unwrap_or('"'), values);
+ match cx.editor.registers.write(register, values) {
+ Ok(_) => cx.editor.set_status(format!(
+ "yanked {selections} selection(s) to register {register}",
+ )),
+ Err(err) => cx.editor.set_error(err.to_string()),
+ }
- cx.editor.set_status(msg);
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 selection = doc.selection(view.id);
+ let selections = selection.len();
let joined = selection
.fragments(text)
.fold(String::new(), |mut acc, fragment| {
@@ -3788,14 +3793,12 @@ fn yank_joined_impl(editor: &mut Editor, separator: &str, register: char) {
acc
});
- let msg = format!(
- "joined and yanked {} selection(s) to register {}",
- selection.len(),
- register,
- );
-
- editor.registers.write(register, vec![joined]);
- editor.set_status(msg);
+ match editor.registers.write(register, vec![joined]) {
+ Ok(_) => editor.set_status(format!(
+ "joined and yanked {selections} selection(s) to register {register}",
+ )),
+ Err(err) => editor.set_error(err.to_string()),
+ }
}
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) {
let count = cx.count();
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);
- exit_select_mode(cx);
+ let Some(values) = cx.editor.registers
+ .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(
@@ -4109,12 +4112,12 @@ fn replace_selections_with_primary_clipboard(cx: &mut Context) {
fn paste(cx: &mut Context, pos: Paste) {
let count = cx.count();
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) {
- paste_impl(values, doc, view, pos, count, cx.editor.mode);
- }
+ let Some(values) = cx.editor.registers.read(reg_name, cx.editor) else { return };
+ 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) {
@@ -5593,9 +5596,12 @@ fn record_macro(cx: &mut Context) {
}
})
.collect::<String>();
- cx.editor.registers.write(reg, vec![s]);
- cx.editor
- .set_status(format!("Recorded to register [{}]", reg));
+ match cx.editor.registers.write(reg, vec![s]) {
+ Ok(_) => cx
+ .editor
+ .set_status(format!("Recorded to register [{}]", reg)),
+ Err(err) => cx.editor.set_error(err.to_string()),
+ }
} else {
let reg = cx.register.take().unwrap_or('@');
cx.editor.macro_recording = Some((reg, Vec::new()));
@@ -5615,8 +5621,14 @@ fn replay_macro(cx: &mut Context) {
return;
}
- let keys: Vec<KeyEvent> = if let Some([keys_str]) = cx.editor.registers.read(reg) {
- match helix_view::input::parse_macro(keys_str) {
+ let keys: Vec<KeyEvent> = if let Some(keys) = cx
+ .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,
Err(err) => {
cx.editor.set_error(format!("Invalid macro: {}", err));
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index 175f8bc6..28759b3f 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -2285,13 +2285,12 @@ fn clear_register(
format!("Invalid register {}", args[0])
);
let register = args[0].chars().next().unwrap_or_default();
- match cx.editor.registers.remove(register) {
- Some(_) => cx
- .editor
- .set_status(format!("Register {} cleared", register)),
- None => cx
- .editor
- .set_error(format!("Register {} not found", register)),
+ if cx.editor.registers.remove(register) {
+ cx.editor
+ .set_status(format!("Register {} cleared", register));
+ } else {
+ cx.editor
+ .set_error(format!("Register {} not found", register));
}
Ok(())
}
diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs
index 1352f493..8dc2906a 100644
--- a/helix-term/src/ui/prompt.rs
+++ b/helix-term/src/ui/prompt.rs
@@ -306,8 +306,8 @@ impl Prompt {
direction: CompletionDirection,
) {
(self.callback_fn)(cx, &self.line, PromptEvent::Abort);
- let values = match cx.editor.registers.read(register) {
- Some(values) if !values.is_empty() => values,
+ let mut values = match cx.editor.registers.read(register, cx.editor) {
+ Some(values) if values.len() > 0 => values,
_ => return,
};
@@ -315,13 +315,16 @@ impl Prompt {
let index = match direction {
CompletionDirection::Forward => self.history_pos.map_or(0, |i| i + 1),
- CompletionDirection::Backward => {
- self.history_pos.unwrap_or(values.len()).saturating_sub(1)
- }
+ CompletionDirection::Backward => self
+ .history_pos
+ .unwrap_or_else(|| values.len())
+ .saturating_sub(1),
}
.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);
@@ -470,7 +473,7 @@ impl Prompt {
// Show the most recently entered value as a suggestion.
if let Some(suggestion) = self
.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);
}
@@ -567,25 +570,29 @@ impl Component for Prompt {
} else {
let last_item = self
.history_register
- .and_then(|reg| cx.editor.registers.last(reg).cloned())
- .map(|entry| entry.into())
- .unwrap_or_else(|| Cow::from(""));
+ .and_then(|reg| cx.editor.registers.last(reg, cx.editor))
+ .map(|entry| entry.to_string())
+ .unwrap_or_else(|| String::from(""));
// handle executing with last command in history if nothing entered
- let input: Cow<str> = if self.line.is_empty() {
- last_item
+ let input = if self.line.is_empty() {
+ &last_item
} else {
if last_item != self.line {
// store in history
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;
}
@@ -617,25 +624,16 @@ impl Component for Prompt {
self.completion = cx
.editor
.registers
- .inner()
- .iter()
- .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())
- })
+ .iter_preview()
+ .map(|(ch, preview)| (0.., format!("{} {}", ch, &preview).into()))
.collect();
self.next_char_handler = Some(Box::new(|prompt, c, context| {
prompt.insert_str(
- context
+ &context
.editor
.registers
- .read(c)
- .and_then(|r| r.first())
- .map_or("", |r| r.as_str()),
+ .first(c, context.editor)
+ .unwrap_or_default(),
context.editor,
);
}));
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 20469ae9..1824ebf7 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -5,6 +5,7 @@ use crate::{
graphics::{CursorKind, Rect},
info::Info,
input::KeyEvent,
+ register::Registers,
theme::{self, Theme},
tree::{self, Tree},
view::ViewPosition,
@@ -40,7 +41,6 @@ use tokio::{
use anyhow::{anyhow, bail, Error};
pub use helix_core::diagnostic::Severity;
-pub use helix_core::register::Registers;
use helix_core::{
auto_pairs::AutoPairs,
syntax::{self, AutoPairConfig, SoftWrap},
diff --git a/helix-view/src/info.rs b/helix-view/src/info.rs
index 1503e855..ca783fef 100644
--- a/helix-view/src/info.rs
+++ b/helix-view/src/info.rs
@@ -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;
#[derive(Debug)]
@@ -56,16 +57,8 @@ impl Info {
pub fn from_registers(registers: &Registers) -> Self {
let body: Vec<_> = registers
- .inner()
- .iter()
- .map(|(ch, reg)| {
- let content = reg
- .read()
- .get(0)
- .and_then(|s| s.lines().next())
- .unwrap_or_default();
- (ch.to_string(), content)
- })
+ .iter_preview()
+ .map(|(ch, preview)| (ch.to_string(), preview))
.collect();
let mut infobox = Self::new("Registers", &body);