feat: the ability to close input using Esc, and the new op of Cut (#6)

This commit is contained in:
三咲雅 · Misaki Masa 2023-07-25 01:25:21 +08:00 committed by GitHub
parent 3b2d141ca4
commit 688afeaf27
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 106 additions and 90 deletions

8
Cargo.lock generated
View file

@ -1502,18 +1502,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.174" version = "1.0.175"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b88756493a5bd5e5395d53baa70b194b05764ab85b59e43e4b8f4e1192fa9b1" checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.174" version = "1.0.175"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e5c3a298c7f978e53536f95a63bdc4c4a64550582f31a0359a9afda6aede62e" checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -125,8 +125,10 @@ keymap = [
{ on = [ "h" ], exec = "move -1" }, { on = [ "h" ], exec = "move -1" },
{ on = [ "l" ], exec = "move 1" }, { on = [ "l" ], exec = "move 1" },
{ on = [ "H" ], exec = "move -999" }, { on = [ "0" ], exec = "move -999" },
{ on = [ "L" ], exec = "move 999" }, { on = [ "$" ], exec = "move 999" },
{ on = [ "I" ], exec = [ "move -999", "insert" ] },
{ on = [ "A" ], exec = [ "move 999", "insert --append" ] },
{ on = [ "<Left>" ], exec = "move -1" }, { on = [ "<Left>" ], exec = "move -1" },
{ on = [ "<Right>" ], exec = "move 1" }, { on = [ "<Right>" ], exec = "move 1" },
@ -135,9 +137,9 @@ keymap = [
{ on = [ "w" ], exec = "forward" }, { on = [ "w" ], exec = "forward" },
{ on = [ "e" ], exec = "forward --end-of-word" }, { on = [ "e" ], exec = "forward --end-of-word" },
{ on = [ "d" ], exec = "delete" }, { on = [ "d" ], exec = "delete --cut" },
{ on = [ "c" ], exec = "delete --insert" }, { on = [ "c" ], exec = "delete --cut --insert" },
{ on = [ "x" ], exec = [ "delete", "move 1 --in-operating" ] }, { on = [ "x" ], exec = [ "delete --cut", "move 1 --in-operating" ] },
{ on = [ "y" ], exec = [ "yank" ] }, { on = [ "y" ], exec = [ "yank" ] },
{ on = [ "p" ], exec = [ "paste" ] }, { on = [ "p" ], exec = [ "paste" ] },

View file

@ -141,6 +141,7 @@
- delete: Delete the selected characters. - delete: Delete the selected characters.
- `--cut`: Cut the selected characters into the clipboard, instead of only deleting them.
- `--insert`: Delete and enter insert mode. - `--insert`: Delete and enter insert mode.
- yank: Copy the selected characters. - yank: Copy the selected characters.

View file

@ -5,7 +5,7 @@ use ratatui::layout::Rect;
use tokio::sync::oneshot::Sender; use tokio::sync::oneshot::Sender;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use super::{InputSnap, InputSnaps}; use super::{mode::InputMode, op::InputOp, InputSnap, InputSnaps};
use crate::{core::{external, Position}, misc::CharKind}; use crate::{core::{external, Position}, misc::CharKind};
#[derive(Default)] #[derive(Default)]
@ -25,26 +25,6 @@ pub struct InputOpt {
pub position: Position, 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 { impl Input {
pub fn show(&mut self, opt: InputOpt, tx: Sender<Result<String>>) { pub fn show(&mut self, opt: InputOpt, tx: Sender<Result<String>>) {
self.close(false); self.close(false);
@ -73,9 +53,11 @@ impl Input {
pub fn escape(&mut self) -> bool { pub fn escape(&mut self) -> bool {
let snap = self.snap_mut(); let snap = self.snap_mut();
match snap.mode { match snap.mode {
InputMode::Normal if snap.op == InputOp::None => {
self.close(false);
}
InputMode::Normal => { InputMode::Normal => {
snap.op = InputOp::None; snap.op = InputOp::None;
snap.start = None;
} }
InputMode::Insert => { InputMode::Insert => {
snap.mode = InputMode::Normal; snap.mode = InputMode::Normal;
@ -104,7 +86,9 @@ impl Input {
if !self.snaps.undo() { if !self.snaps.undo() {
return false; return false;
} }
self.escape(); if self.snap().mode == InputMode::Insert {
self.escape();
}
true true
} }
@ -113,7 +97,6 @@ impl Input {
if !self.snaps.redo() { if !self.snaps.redo() {
return false; return false;
} }
self.escape();
true true
} }
@ -188,7 +171,7 @@ impl Input {
} else { } else {
c != CharKind::Space && c != prev 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); return self.move_(i as isize);
} else if b { } else if b {
return self.move_(if end { i - 1 } else { i } as isize); return self.move_(if end { i - 1 } else { i } as isize);
@ -225,24 +208,19 @@ impl Input {
self.move_(-1) 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 { match self.snap().op {
InputOp::None => { InputOp::None => {
if self.snap().start.is_some() { self.snap_mut().op = InputOp::Delete(cut, insert, self.snap().cursor);
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);
false 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(..) => { InputOp::Delete(..) => {
self.move_(-(self.snap().len() as isize)); self.snap_mut().op = InputOp::Delete(cut, insert, 0);
self.snap_mut().value.clear(); return self.move_(self.snap().len() as isize);
self.snap_mut().mode = if insert { InputMode::Insert } else { InputMode::Normal };
true
} }
_ => false, _ => false,
} }
@ -251,18 +229,15 @@ impl Input {
pub fn yank(&mut self) -> bool { pub fn yank(&mut self) -> bool {
match self.snap().op { match self.snap().op {
InputOp::None => { InputOp::None => {
if self.snap().start.is_some() { self.snap_mut().op = InputOp::Yank(self.snap().cursor);
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);
false false
} }
InputOp::Yank => { InputOp::Select(start) => {
self.snap_mut().start = Some(0); 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); self.move_(self.snap().len() as isize);
false false
} }
@ -271,8 +246,8 @@ impl Input {
} }
pub fn paste(&mut self, before: bool) -> bool { pub fn paste(&mut self, before: bool) -> bool {
if self.snap().start.is_some() { if let Some(start) = self.snap().op.start() {
self.snap_mut().op = InputOp::Delete(false); self.snap_mut().op = InputOp::Delete(false, false, start);
self.handle_op(self.snap().cursor, true); self.handle_op(self.snap().cursor, true);
} }
@ -293,38 +268,39 @@ impl Input {
fn handle_op(&mut self, cursor: usize, include: bool) -> bool { fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
let old = self.snap().clone(); let old = self.snap().clone();
let snap = self.snap_mut(); let snap = self.snap_mut();
let range = if snap.op == InputOp::None { None } else { snap.range(cursor, include) };
match snap.op { match snap.op {
InputOp::None => { InputOp::None | InputOp::Select(_) => {
snap.cursor = cursor; snap.cursor = cursor;
} }
InputOp::Delete(insert) => { InputOp::Delete(cut, insert, _) => {
let range = range.unwrap(); let range = snap.op.range(cursor, include).unwrap();
let Range { start, end } = snap.idx(range.start)..snap.idx(range.end); 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::<String>();
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.mode = if insert { InputMode::Insert } else { InputMode::Normal };
snap.cursor = range.start; snap.cursor = range.start;
} }
InputOp::Yank => { InputOp::Yank(_) => {
let range = range.unwrap(); let range = snap.op.range(cursor, include).unwrap();
let Range { start, end } = snap.idx(range.start)..snap.idx(range.end); let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
let yanked = &snap.value[start.unwrap()..end.unwrap()]; let yanked = &snap.value[start.unwrap()..end.unwrap()];
futures::executor::block_on(async { snap.op = InputOp::None;
external::clipboard_set(yanked).await.ok(); 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); snap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor);
if *snap == old { if *snap == old {
return false; return false;
} }
if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
if old.op != InputOp::None {
self.snaps.tag(); self.snaps.tag();
} }
true true
@ -358,11 +334,12 @@ impl Input {
pub fn selected(&self) -> Option<Rect> { pub fn selected(&self) -> Option<Rect> {
let snap = self.snap(); let snap = self.snap();
if snap.start.is_none() { let start = if let Some(s) = snap.op.start() {
s
} else {
return None; return None;
} };
let start = snap.start.unwrap();
let (start, end) = let (start, end) =
if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) }; if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) };

View file

@ -1,7 +1,11 @@
mod input; mod input;
mod mode;
mod op;
mod snap; mod snap;
mod snaps; mod snaps;
pub use input::*; pub use input::*;
pub use mode::*;
use op::*;
use snap::*; use snap::*;
use snaps::*; use snaps::*;

11
src/core/input/mode.rs Normal file
View file

@ -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 }
}

31
src/core/input/op.rs Normal file
View file

@ -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<usize> {
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<Range<usize>> {
self
.start()
.map(|s| if s <= cursor { (s, cursor) } else { (cursor, s) })
.map(|(s, e)| s..e + include as usize)
}
}

View file

@ -8,8 +8,7 @@ use super::{InputMode, InputOp};
pub(super) struct InputSnap { pub(super) struct InputSnap {
pub(super) value: String, pub(super) value: String,
pub(super) op: InputOp, pub(super) op: InputOp,
pub(super) start: Option<usize>,
pub(super) mode: InputMode, pub(super) mode: InputMode,
pub(super) offset: usize, pub(super) offset: usize,
@ -22,7 +21,6 @@ impl InputSnap {
value, value,
op: Default::default(), op: Default::default(),
start: Default::default(),
mode: Default::default(), mode: Default::default(),
offset: usize::MAX, offset: usize::MAX,
@ -45,7 +43,6 @@ impl InputSnap {
} }
self.op = InputOp::None; self.op = InputOp::None;
self.start = None;
self.mode = InputMode::Insert; self.mode = InputMode::Insert;
true true
} }
@ -57,7 +54,7 @@ impl InputSnap {
return false; return false;
} }
self.start = Some(self.cursor); self.op = InputOp::Select(self.cursor);
true true
} }
} }
@ -88,15 +85,6 @@ impl InputSnap {
#[inline] #[inline]
pub(super) fn rev(&self) -> String { self.value.chars().rev().collect::<String>() } pub(super) fn rev(&self) -> String { self.value.chars().rev().collect::<String>() }
#[inline]
pub(super) fn range(&mut self, cursor: usize, include: bool) -> Option<Range<usize>> {
self
.start
.take()
.map(|s| if s <= cursor { (s, cursor) } else { (cursor, s) })
.map(|(s, e)| s..e + include as usize)
}
#[inline] #[inline]
pub(super) fn window(&self) -> Range<usize> { Self::find_window(&self.value, self.offset) } pub(super) fn window(&self) -> Range<usize> { Self::find_window(&self.value, self.offset) }

View file

@ -192,7 +192,9 @@ impl Executor {
"backward" => cx.input.backward(), "backward" => cx.input.backward(),
"forward" => cx.input.forward(exec.named.contains_key("end-of-word")), "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(), "yank" => cx.input.yank(),
"paste" => cx.input.paste(exec.named.contains_key("before")), "paste" => cx.input.paste(exec.named.contains_key("before")),