diff --git a/Cargo.lock b/Cargo.lock index eb333e10..2865f08a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1502,18 +1502,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.174" +version = "1.0.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b88756493a5bd5e5395d53baa70b194b05764ab85b59e43e4b8f4e1192fa9b1" +checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.174" +version = "1.0.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5c3a298c7f978e53536f95a63bdc4c4a64550582f31a0359a9afda6aede62e" +checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4" dependencies = [ "proc-macro2", "quote", diff --git a/config/keymap.toml b/config/keymap.toml index d20d5c2d..5ca5dc63 100644 --- a/config/keymap.toml +++ b/config/keymap.toml @@ -125,8 +125,10 @@ keymap = [ { on = [ "h" ], exec = "move -1" }, { on = [ "l" ], exec = "move 1" }, - { on = [ "H" ], exec = "move -999" }, - { on = [ "L" ], exec = "move 999" }, + { on = [ "0" ], exec = "move -999" }, + { on = [ "$" ], exec = "move 999" }, + { on = [ "I" ], exec = [ "move -999", "insert" ] }, + { on = [ "A" ], exec = [ "move 999", "insert --append" ] }, { on = [ "" ], exec = "move -1" }, { on = [ "" ], exec = "move 1" }, @@ -135,9 +137,9 @@ keymap = [ { on = [ "w" ], exec = "forward" }, { on = [ "e" ], exec = "forward --end-of-word" }, - { on = [ "d" ], exec = "delete" }, - { on = [ "c" ], exec = "delete --insert" }, - { on = [ "x" ], exec = [ "delete", "move 1 --in-operating" ] }, + { on = [ "d" ], exec = "delete --cut" }, + { on = [ "c" ], exec = "delete --cut --insert" }, + { on = [ "x" ], exec = [ "delete --cut", "move 1 --in-operating" ] }, { on = [ "y" ], exec = [ "yank" ] }, { on = [ "p" ], exec = [ "paste" ] }, diff --git a/docs/keymap.md b/docs/keymap.md index 65b10e79..a187cdf4 100644 --- a/docs/keymap.md +++ b/docs/keymap.md @@ -141,6 +141,7 @@ - delete: Delete the selected characters. + - `--cut`: Cut the selected characters into the clipboard, instead of only deleting them. - `--insert`: Delete and enter insert mode. - yank: Copy the selected characters. diff --git a/src/core/input/input.rs b/src/core/input/input.rs index 33499685..28b54069 100644 --- a/src/core/input/input.rs +++ b/src/core/input/input.rs @@ -5,7 +5,7 @@ use ratatui::layout::Rect; use tokio::sync::oneshot::Sender; use unicode_width::UnicodeWidthStr; -use super::{InputSnap, InputSnaps}; +use super::{mode::InputMode, op::InputOp, InputSnap, InputSnaps}; use crate::{core::{external, Position}, misc::CharKind}; #[derive(Default)] @@ -25,26 +25,6 @@ pub struct InputOpt { pub position: Position, } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub enum InputMode { - Normal, - #[default] - Insert, -} - -impl InputMode { - #[inline] - pub(super) fn delta(&self) -> usize { (*self != InputMode::Insert) as usize } -} - -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub enum InputOp { - #[default] - None, - Delete(bool), - Yank, -} - impl Input { pub fn show(&mut self, opt: InputOpt, tx: Sender>) { self.close(false); @@ -73,9 +53,11 @@ impl Input { pub fn escape(&mut self) -> bool { let snap = self.snap_mut(); match snap.mode { + InputMode::Normal if snap.op == InputOp::None => { + self.close(false); + } InputMode::Normal => { snap.op = InputOp::None; - snap.start = None; } InputMode::Insert => { snap.mode = InputMode::Normal; @@ -104,7 +86,9 @@ impl Input { if !self.snaps.undo() { return false; } - self.escape(); + if self.snap().mode == InputMode::Insert { + self.escape(); + } true } @@ -113,7 +97,6 @@ impl Input { if !self.snaps.redo() { return false; } - self.escape(); true } @@ -188,7 +171,7 @@ impl Input { } else { c != CharKind::Space && c != prev }; - if b && snap.op != InputOp::None { + if b && !matches!(snap.op, InputOp::None | InputOp::Select(_)) { return self.move_(i as isize); } else if b { return self.move_(if end { i - 1 } else { i } as isize); @@ -225,24 +208,19 @@ impl Input { self.move_(-1) } - pub fn delete(&mut self, insert: bool) -> bool { + pub fn delete(&mut self, cut: bool, insert: bool) -> bool { match self.snap().op { InputOp::None => { - if self.snap().start.is_some() { - self.snap_mut().op = InputOp::Delete(insert); - return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some(); - } - - let snap = self.snap_mut(); - snap.op = InputOp::Delete(insert); - snap.start = Some(snap.cursor); + self.snap_mut().op = InputOp::Delete(cut, insert, self.snap().cursor); false } + InputOp::Select(start) => { + self.snap_mut().op = InputOp::Delete(cut, insert, start); + return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some(); + } InputOp::Delete(..) => { - self.move_(-(self.snap().len() as isize)); - self.snap_mut().value.clear(); - self.snap_mut().mode = if insert { InputMode::Insert } else { InputMode::Normal }; - true + self.snap_mut().op = InputOp::Delete(cut, insert, 0); + return self.move_(self.snap().len() as isize); } _ => false, } @@ -251,18 +229,15 @@ impl Input { pub fn yank(&mut self) -> bool { match self.snap().op { InputOp::None => { - if self.snap().start.is_some() { - self.snap_mut().op = InputOp::Yank; - return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some(); - } - - let snap = self.snap_mut(); - snap.op = InputOp::Yank; - snap.start = Some(snap.cursor); + self.snap_mut().op = InputOp::Yank(self.snap().cursor); false } - InputOp::Yank => { - self.snap_mut().start = Some(0); + InputOp::Select(start) => { + self.snap_mut().op = InputOp::Yank(start); + return self.handle_op(self.snap().cursor, true).then(|| self.move_(0)).is_some(); + } + InputOp::Yank(_) => { + self.snap_mut().op = InputOp::Yank(0); self.move_(self.snap().len() as isize); false } @@ -271,8 +246,8 @@ impl Input { } pub fn paste(&mut self, before: bool) -> bool { - if self.snap().start.is_some() { - self.snap_mut().op = InputOp::Delete(false); + if let Some(start) = self.snap().op.start() { + self.snap_mut().op = InputOp::Delete(false, false, start); self.handle_op(self.snap().cursor, true); } @@ -293,38 +268,39 @@ impl Input { fn handle_op(&mut self, cursor: usize, include: bool) -> bool { let old = self.snap().clone(); let snap = self.snap_mut(); - let range = if snap.op == InputOp::None { None } else { snap.range(cursor, include) }; match snap.op { - InputOp::None => { + InputOp::None | InputOp::Select(_) => { snap.cursor = cursor; } - InputOp::Delete(insert) => { - let range = range.unwrap(); + InputOp::Delete(cut, insert, _) => { + let range = snap.op.range(cursor, include).unwrap(); let Range { start, end } = snap.idx(range.start)..snap.idx(range.end); - snap.value.drain(start.unwrap()..end.unwrap()); + let drain = snap.value.drain(start.unwrap()..end.unwrap()).collect::(); + if cut { + futures::executor::block_on(async { external::clipboard_set(&drain).await.ok() }); + } + + snap.op = InputOp::None; snap.mode = if insert { InputMode::Insert } else { InputMode::Normal }; snap.cursor = range.start; } - InputOp::Yank => { - let range = range.unwrap(); + InputOp::Yank(_) => { + let range = snap.op.range(cursor, include).unwrap(); let Range { start, end } = snap.idx(range.start)..snap.idx(range.end); let yanked = &snap.value[start.unwrap()..end.unwrap()]; - futures::executor::block_on(async { - external::clipboard_set(yanked).await.ok(); - }); + snap.op = InputOp::None; + futures::executor::block_on(async { external::clipboard_set(yanked).await.ok() }); } }; - snap.op = InputOp::None; snap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor); if *snap == old { return false; } - - if old.op != InputOp::None { + if !matches!(old.op, InputOp::None | InputOp::Select(_)) { self.snaps.tag(); } true @@ -358,11 +334,12 @@ impl Input { pub fn selected(&self) -> Option { let snap = self.snap(); - if snap.start.is_none() { + let start = if let Some(s) = snap.op.start() { + s + } else { return None; - } + }; - let start = snap.start.unwrap(); let (start, end) = if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) }; diff --git a/src/core/input/mod.rs b/src/core/input/mod.rs index 4aabaa08..412dd600 100644 --- a/src/core/input/mod.rs +++ b/src/core/input/mod.rs @@ -1,7 +1,11 @@ mod input; +mod mode; +mod op; mod snap; mod snaps; pub use input::*; +pub use mode::*; +use op::*; use snap::*; use snaps::*; diff --git a/src/core/input/mode.rs b/src/core/input/mode.rs new file mode 100644 index 00000000..4668143b --- /dev/null +++ b/src/core/input/mode.rs @@ -0,0 +1,11 @@ +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum InputMode { + Normal, + #[default] + Insert, +} + +impl InputMode { + #[inline] + pub(super) fn delta(&self) -> usize { (*self != InputMode::Insert) as usize } +} diff --git a/src/core/input/op.rs b/src/core/input/op.rs new file mode 100644 index 00000000..7a2fbae2 --- /dev/null +++ b/src/core/input/op.rs @@ -0,0 +1,31 @@ +use std::ops::Range; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum InputOp { + #[default] + None, + Select(usize), + // cut, insert, start + Delete(bool, bool, usize), + Yank(usize), +} + +impl InputOp { + #[inline] + pub(super) fn start(&self) -> Option { + match self { + InputOp::None => None, + InputOp::Select(s) => Some(*s), + InputOp::Delete(.., s) => Some(*s), + InputOp::Yank(s) => Some(*s), + } + } + + #[inline] + pub(super) fn range(&self, cursor: usize, include: bool) -> Option> { + self + .start() + .map(|s| if s <= cursor { (s, cursor) } else { (cursor, s) }) + .map(|(s, e)| s..e + include as usize) + } +} diff --git a/src/core/input/snap.rs b/src/core/input/snap.rs index c100a8d2..a5a9b510 100644 --- a/src/core/input/snap.rs +++ b/src/core/input/snap.rs @@ -8,8 +8,7 @@ use super::{InputMode, InputOp}; pub(super) struct InputSnap { pub(super) value: String, - pub(super) op: InputOp, - pub(super) start: Option, + pub(super) op: InputOp, pub(super) mode: InputMode, pub(super) offset: usize, @@ -22,7 +21,6 @@ impl InputSnap { value, op: Default::default(), - start: Default::default(), mode: Default::default(), offset: usize::MAX, @@ -45,7 +43,6 @@ impl InputSnap { } self.op = InputOp::None; - self.start = None; self.mode = InputMode::Insert; true } @@ -57,7 +54,7 @@ impl InputSnap { return false; } - self.start = Some(self.cursor); + self.op = InputOp::Select(self.cursor); true } } @@ -88,15 +85,6 @@ impl InputSnap { #[inline] pub(super) fn rev(&self) -> String { self.value.chars().rev().collect::() } - #[inline] - pub(super) fn range(&mut self, cursor: usize, include: bool) -> Option> { - self - .start - .take() - .map(|s| if s <= cursor { (s, cursor) } else { (cursor, s) }) - .map(|(s, e)| s..e + include as usize) - } - #[inline] pub(super) fn window(&self) -> Range { Self::find_window(&self.value, self.offset) } diff --git a/src/ui/dispatcher.rs b/src/ui/dispatcher.rs index 2b1af850..20343c4e 100644 --- a/src/ui/dispatcher.rs +++ b/src/ui/dispatcher.rs @@ -192,7 +192,9 @@ impl Executor { "backward" => cx.input.backward(), "forward" => cx.input.forward(exec.named.contains_key("end-of-word")), - "delete" => cx.input.delete(exec.named.contains_key("insert")), + "delete" => { + cx.input.delete(exec.named.contains_key("cut"), exec.named.contains_key("insert")) + } "yank" => cx.input.yank(), "paste" => cx.input.paste(exec.named.contains_key("before")),