mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: the ability to close input using Esc, and the new op of Cut (#6)
This commit is contained in:
parent
3b2d141ca4
commit
688afeaf27
9 changed files with 106 additions and 90 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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" ] },
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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) };
|
||||
|
||||
|
|
|
|||
|
|
@ -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
11
src/core/input/mode.rs
Normal 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
31
src/core/input/op.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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")),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue