Implemented proper behavior for deleting/yanking lines into registers and putting them

This commit is contained in:
2026-02-25 15:54:31 -05:00
parent 22adbce9e4
commit 86c9fe281a
3 changed files with 167 additions and 55 deletions

View File

@@ -14,7 +14,7 @@ use crate::{
libsh::{
error::ShResult,
term::{Style, Styled},
}, parse::lex::{LexFlags, LexStream, Tk, TkFlags, TkRule}, prelude::*, prompt::readline::{markers, register::write_register}, state::read_shopts
}, parse::lex::{LexFlags, LexStream, Tk, TkFlags, TkRule}, prelude::*, prompt::readline::{markers, register::{write_register, RegisterContent}}, state::read_shopts
};
const PUNCTUATION: [&str; 3] = ["?", "!", "."];
@@ -2441,7 +2441,7 @@ impl LineBuf {
do_indent = read_shopts(|o| o.prompt.auto_indent);
}
let register_text = if verb == Verb::Yank {
let text = if verb == Verb::Yank {
self
.slice(start..end)
.map(|c| c.to_string())
@@ -2451,7 +2451,16 @@ impl LineBuf {
self.update_graphemes();
drained
};
register.write_to_register(register_text);
let is_linewise = matches!(
motion,
MotionKind::InclusiveWithTargetCol(..) | MotionKind::ExclusiveWithTargetCol(..)
);
let register_content = if is_linewise {
RegisterContent::Line(text)
} else {
RegisterContent::Span(text)
};
register.write_to_register(register_content);
self.cursor.set(start);
if do_indent {
self.calc_indent_level();
@@ -2630,22 +2639,46 @@ impl LineBuf {
let Some(content) = register.read_from_register() else {
return Ok(());
};
if content.is_empty() {
return Ok(());
}
if let Some(range) = self.select_range {
let register_text = self.drain_inclusive(range.0..=range.1);
write_register(None, register_text); // swap deleted text into register
write_register(None, RegisterContent::Span(register_text)); // swap deleted text into register
self.insert_str_at(range.0, &content);
self.cursor.set(range.0 + content.chars().count());
let text = content.as_str();
self.insert_str_at(range.0, text);
self.cursor.set(range.0 + content.char_count());
self.select_range = None;
self.update_graphemes();
return Ok(());
}
let insert_idx = match anchor {
Anchor::After => self.cursor.ret_add(1),
Anchor::Before => self.cursor.get(),
};
self.insert_str_at(insert_idx, &content);
self.cursor.add(content.len().saturating_sub(1));
match content {
RegisterContent::Span(ref text) => {
let insert_idx = match anchor {
Anchor::After => self.cursor.ret_add(1),
Anchor::Before => self.cursor.get(),
};
self.insert_str_at(insert_idx, text);
self.cursor.add(text.len().saturating_sub(1));
}
RegisterContent::Line(ref text) => {
let insert_idx = match anchor {
Anchor::After => self.end_of_line(),
Anchor::Before => self.start_of_line(),
};
let needs_newline = self.grapheme_before(insert_idx).is_some_and(|gr| gr != "\n");
if needs_newline {
let full = format!("\n{}", text);
self.insert_str_at(insert_idx, &full);
self.cursor.set(insert_idx + 1);
} else {
self.insert_str_at(insert_idx, text);
self.cursor.set(insert_idx);
}
}
RegisterContent::Empty => {}
}
}
Verb::SwapVisualAnchor => {
if let Some((start, end)) = self.select_range()

View File

@@ -1,26 +1,84 @@
use std::sync::Mutex;
use std::{fmt::Display, sync::Mutex};
pub static REGISTERS: Mutex<Registers> = Mutex::new(Registers::new());
pub fn read_register(ch: Option<char>) -> Option<String> {
pub fn read_register(ch: Option<char>) -> Option<RegisterContent> {
let lock = REGISTERS.lock().unwrap();
lock.get_reg(ch).map(|r| r.buf().clone())
lock.get_reg(ch).map(|r| r.content().clone())
}
pub fn write_register(ch: Option<char>, buf: String) {
pub fn write_register(ch: Option<char>, buf: RegisterContent) {
let mut lock = REGISTERS.lock().unwrap();
if let Some(r) = lock.get_reg_mut(ch) {
r.write(buf)
}
}
pub fn append_register(ch: Option<char>, buf: String) {
pub fn append_register(ch: Option<char>, buf: RegisterContent) {
let mut lock = REGISTERS.lock().unwrap();
if let Some(r) = lock.get_reg_mut(ch) {
r.append(buf)
}
}
#[derive(Default, Clone, Debug)]
pub enum RegisterContent {
Span(String),
Line(String),
#[default]
Empty,
}
impl Display for RegisterContent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Span(s) => write!(f, "{}", s),
Self::Line(s) => write!(f, "{}", s),
Self::Empty => write!(f, ""),
}
}
}
impl RegisterContent {
pub fn clear(&mut self) {
match self {
Self::Span(s) => s.clear(),
Self::Line(s) => s.clear(),
Self::Empty => {}
}
}
pub fn len(&self) -> usize {
match self {
Self::Span(s) => s.len(),
Self::Line(s) => s.len(),
Self::Empty => 0,
}
}
pub fn is_empty(&self) -> bool {
match self {
Self::Span(s) => s.is_empty(),
Self::Line(s) => s.is_empty(),
Self::Empty => true,
}
}
pub fn is_line(&self) -> bool {
matches!(self, Self::Line(_))
}
pub fn is_span(&self) -> bool {
matches!(self, Self::Span(_))
}
pub fn as_str(&self) -> &str {
match self {
Self::Span(s) => s,
Self::Line(s) => s,
Self::Empty => "",
}
}
pub fn char_count(&self) -> usize {
self.as_str().chars().count()
}
}
#[derive(Default, Debug)]
pub struct Registers {
default: Register,
@@ -55,33 +113,33 @@ pub struct Registers {
impl Registers {
pub const fn new() -> Self {
Self {
default: Register(String::new()),
a: Register(String::new()),
b: Register(String::new()),
c: Register(String::new()),
d: Register(String::new()),
e: Register(String::new()),
f: Register(String::new()),
g: Register(String::new()),
h: Register(String::new()),
i: Register(String::new()),
j: Register(String::new()),
k: Register(String::new()),
l: Register(String::new()),
m: Register(String::new()),
n: Register(String::new()),
o: Register(String::new()),
p: Register(String::new()),
q: Register(String::new()),
r: Register(String::new()),
s: Register(String::new()),
t: Register(String::new()),
u: Register(String::new()),
v: Register(String::new()),
w: Register(String::new()),
x: Register(String::new()),
y: Register(String::new()),
z: Register(String::new()),
default: Register::new(),
a: Register::new(),
b: Register::new(),
c: Register::new(),
d: Register::new(),
e: Register::new(),
f: Register::new(),
g: Register::new(),
h: Register::new(),
i: Register::new(),
j: Register::new(),
k: Register::new(),
l: Register::new(),
m: Register::new(),
n: Register::new(),
o: Register::new(),
p: Register::new(),
q: Register::new(),
r: Register::new(),
s: Register::new(),
t: Register::new(),
u: Register::new(),
v: Register::new(),
w: Register::new(),
x: Register::new(),
y: Register::new(),
z: Register::new(),
}
}
pub fn get_reg(&self, ch: Option<char>) -> Option<&Register> {
@@ -155,18 +213,39 @@ impl Registers {
}
#[derive(Clone, Default, Debug)]
pub struct Register(String);
pub struct Register {
content: RegisterContent,
}
impl Register {
pub fn buf(&self) -> &String {
&self.0
pub const fn new() -> Self {
Self {
content: RegisterContent::Span(String::new()),
}
}
pub fn write(&mut self, buf: String) {
self.0 = buf
pub fn content(&self) -> &RegisterContent {
&self.content
}
pub fn append(&mut self, buf: String) {
self.0.push_str(&buf)
pub fn write(&mut self, buf: RegisterContent) {
self.content = buf
}
pub fn append(&mut self, buf: RegisterContent) {
match buf {
RegisterContent::Empty => {}
RegisterContent::Span(ref s) | RegisterContent::Line(ref s) => match &mut self.content {
RegisterContent::Empty => self.content = buf,
RegisterContent::Span(existing) => existing.push_str(s),
RegisterContent::Line(existing) => existing.push_str(s),
},
}
}
pub fn clear(&mut self) {
self.0.clear()
self.content.clear()
}
pub fn is_line(&self) -> bool {
self.content.is_line()
}
pub fn is_span(&self) -> bool {
self.content.is_span()
}
}

View File

@@ -1,6 +1,6 @@
use bitflags::bitflags;
use super::register::{append_register, read_register, write_register};
use super::register::{append_register, read_register, write_register, RegisterContent};
//TODO: write tests that take edit results and cursor positions from actual
// neovim edits and test them against the behavior of this editor
@@ -35,14 +35,14 @@ impl RegisterName {
pub fn count(&self) -> usize {
self.count
}
pub fn write_to_register(&self, buf: String) {
pub fn write_to_register(&self, buf: RegisterContent) {
if self.append {
append_register(self.name, buf);
} else {
write_register(self.name, buf);
}
}
pub fn read_from_register(&self) -> Option<String> {
pub fn read_from_register(&self) -> Option<RegisterContent> {
read_register(self.name)
}
}