mirror of
https://github.com/ilyakooo0/helix.git
synced 2024-08-17 03:50:21 +03:00
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.
This commit is contained in:
parent
0f19f282cf
commit
baceb02a09
@ -18,7 +18,6 @@
|
||||
pub mod object;
|
||||
pub mod path;
|
||||
mod position;
|
||||
pub mod register;
|
||||
pub mod search;
|
||||
pub mod selection;
|
||||
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> {
|
||||
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 '{}'", '/', ®ex);
|
||||
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(®ex);
|
||||
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('"')
|
||||
);
|
||||
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
|
||||
.registers
|
||||
.write(cx.register.unwrap_or('"'), values);
|
||||
|
||||
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 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 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 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));
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -306,8 +306,8 @@ pub fn change_history(
|
||||
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 @@ pub fn change_history(
|
||||
|
||||
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 @@ pub fn render_prompt(&self, area: Rect, surface: &mut Surface, cx: &mut Context)
|
||||
// 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 @@ fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
|
||||
} 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 @@ fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult {
|
||||
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,
|
||||
);
|
||||
}));
|
||||
|
@ -5,6 +5,7 @@
|
||||
graphics::{CursorKind, Rect},
|
||||
info::Info,
|
||||
input::KeyEvent,
|
||||
register::Registers,
|
||||
theme::{self, Theme},
|
||||
tree::{self, Tree},
|
||||
view::ViewPosition,
|
||||
@ -40,7 +41,6 @@
|
||||
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},
|
||||
|
@ -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 @@ pub fn new<T, U>(title: &str, body: &[(T, U)]) -> Self
|
||||
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user