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]]
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",

View file

@ -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 = [ "<Left>" ], exec = "move -1" },
{ on = [ "<Right>" ], 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" ] },

View file

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

View file

@ -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<Result<String>>) {
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::<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.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<Rect> {
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) };

View file

@ -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::*;

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) value: String,
pub(super) op: InputOp,
pub(super) start: Option<usize>,
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::<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]
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(),
"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")),