diff --git a/yazi-core/src/input/commands/mod.rs b/yazi-core/src/input/commands/mod.rs index da2185be..feca8424 100644 --- a/yazi-core/src/input/commands/mod.rs +++ b/yazi-core/src/input/commands/mod.rs @@ -8,6 +8,7 @@ mod insert; mod move_; mod paste; mod redo; +mod type_; mod undo; mod visual; mod yank; diff --git a/yazi-core/src/input/commands/type_.rs b/yazi-core/src/input/commands/type_.rs new file mode 100644 index 00000000..f4b90ed8 --- /dev/null +++ b/yazi-core/src/input/commands/type_.rs @@ -0,0 +1,161 @@ +use std::ops::RangeBounds; + +use crossterm::event::KeyCode; +use yazi_config::keymap::{Exec, Key}; +use yazi_shared::CharKind; + +use crate::input::{Input, InputMode}; + +pub struct Opt; + +impl From<&Exec> for Opt { + fn from(_: &Exec) -> Self { Self } +} + +impl Input { + /// Searches for a word boundary and returns the movement in the cursor + /// position. + /// + /// A word boundary is where the [`CharKind`] changes. + /// + /// If `skip_whitespace_first` is true, we skip initial whitespace. + /// Otherwise, we skip whitespace after reaching a word boundary. + /// + /// If `stop_before_boundary` is true, returns how many characters the cursor + /// needs to move to be at the character *BEFORE* the word boundary, or until + /// the end of the iterator. + /// + /// Otherwise, returns how many characters to move to reach right *AFTER* the + /// word boundary, or the end of the iterator. + fn find_word_boundary(input: impl Iterator + Clone) -> usize { + fn count_spaces(input: impl Iterator) -> usize { + // Move until we don't see any more whitespace. + input.take_while(|&c| CharKind::new(c) == CharKind::Space).count() + } + + fn count_characters(mut input: impl Iterator) -> usize { + // Determine the current character class. + let first = match input.next() { + Some(c) => CharKind::new(c), + None => return 0, + }; + + // Move until we see a different character class or the end of the iterator. + input.take_while(|&c| CharKind::new(c) == first).count() + 1 + } + + let spaces = count_spaces(input.clone()); + spaces + count_characters(input.skip(spaces)) + } + + fn delete_range(&mut self, range: impl RangeBounds) -> bool { + let snap = self.snap_mut(); + snap.cursor = match range.start_bound() { + std::ops::Bound::Included(i) => *i, + std::ops::Bound::Excluded(_) => unreachable!(), + std::ops::Bound::Unbounded => 0, + }; + if snap.value.drain(range).next().is_some() { + self.flush_value(); + return true; + } + false + } + + fn backspace(&mut self, under: bool) -> bool { + let snap = self.snaps.current_mut(); + if !under && snap.cursor < 1 { + return false; + } else if under && snap.cursor >= snap.value.len() { + return false; + } + + if under { + snap.value.remove(snap.idx(snap.cursor).unwrap()); + self.move_(0); + } else { + snap.value.remove(snap.idx(snap.cursor - 1).unwrap()); + self.move_(-1); + } + + self.flush_value(); + true + } + + pub fn type_(&mut self, key: &Key) -> bool { + if self.mode() != InputMode::Insert { + return false; + } + + if let Some(c) = key.plain() { + let mut bits = [0; 4]; + return self.type_str(c.encode_utf8(&mut bits)); + } + + use KeyCode::{Backspace, Char as C}; + + match key { + // Move to the start of the line + Key { code: C('a'), shift: false, ctrl: true, alt: false } => self.move_(isize::MIN), + // Move to the end of the line + Key { code: C('e'), shift: false, ctrl: true, alt: false } => self.move_(isize::MAX), + + // Move back a character + Key { code: C('b'), shift: false, ctrl: true, alt: false } => self.move_(-1), + // Move forward a character + Key { code: C('f'), shift: false, ctrl: true, alt: false } => self.move_(1), + + // Delete the character before the cursor + Key { code: Backspace, shift: false, ctrl: false, alt: false } => self.backspace(false), + Key { code: C('h'), shift: false, ctrl: true, alt: false } => self.backspace(false), + // Delete the character under the cursor + Key { code: C('d'), shift: false, ctrl: true, alt: false } => self.backspace(true), + + // Move back to the start of the current or previous word + Key { code: C('b'), shift: false, ctrl: false, alt: true } => { + let snap = self.snap(); + let idx = snap.idx(snap.cursor).unwrap_or(snap.len()); + + let step = Self::find_word_boundary(snap.value[..idx].chars().rev()); + self.move_(-(step as isize)) + } + // Move forward to the end of the next word + Key { code: C('f'), shift: false, ctrl: false, alt: true } => { + let snap = self.snap(); + let step = Self::find_word_boundary(snap.value.chars().skip(snap.cursor)); + self.move_(step as isize) + } + + // Kill backwards to the start of the line + Key { code: C('u'), shift: false, ctrl: true, alt: false } => { + let snap = self.snap_mut(); + let end = snap.idx(snap.cursor).unwrap_or(snap.len()); + self.delete_range(..end) + } + // Kill forwards to the end of the line + Key { code: C('k'), shift: false, ctrl: true, alt: false } => { + let snap = self.snap_mut(); + let start = snap.idx(snap.cursor).unwrap_or(snap.len()); + self.delete_range(start..) + } + + // Kill backwards to the start of the current word + Key { code: C('w'), shift: false, ctrl: true, alt: false } + | Key { code: Backspace, shift: false, ctrl: false, alt: true } => { + let snap = self.snap_mut(); + let end = snap.idx(snap.cursor).unwrap_or(snap.len()); + let start = end - Self::find_word_boundary(snap.value[..end].chars().rev()); + self.delete_range(start..end) + } + // Kill forwards to the end of the current word + Key { code: C('d'), shift: false, ctrl: false, alt: true } => { + let snap = self.snap_mut(); + let start = snap.idx(snap.cursor).unwrap_or(snap.len()); + let end = start + Self::find_word_boundary(snap.value[start..].chars()); + self.delete_range(start..end) + } + + _ => false, + } + } +} diff --git a/yazi-core/src/input/input.rs b/yazi-core/src/input/input.rs index 08476fea..2a019c79 100644 --- a/yazi-core/src/input/input.rs +++ b/yazi-core/src/input/input.rs @@ -1,9 +1,7 @@ use std::ops::Range; -use crossterm::event::KeyCode; use tokio::sync::mpsc::UnboundedSender; use unicode_width::UnicodeWidthStr; -use yazi_config::keymap::Key; use yazi_shared::InputError; use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps}; @@ -45,22 +43,6 @@ impl Input { self.highlight = opt.highlight; } - pub fn type_(&mut self, key: &Key) -> bool { - if self.mode() != InputMode::Insert { - return false; - } - - if let Some(c) = key.plain() { - let mut bits = [0; 4]; - return self.type_str(c.encode_utf8(&mut bits)); - } - - match key { - Key { code: KeyCode::Backspace, shift: false, ctrl: false, alt: false } => self.backspace(), - _ => false, - } - } - pub fn type_str(&mut self, s: &str) -> bool { let snap = self.snaps.current_mut(); if snap.cursor < 1 { @@ -74,19 +56,6 @@ impl Input { true } - pub fn backspace(&mut self) -> bool { - let snap = self.snaps.current_mut(); - if snap.cursor < 1 { - return false; - } else { - snap.value.remove(snap.idx(snap.cursor - 1).unwrap()); - } - - self.move_(-1); - self.flush_value(); - true - } - pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool { let old = self.snap().clone(); let snap = self.snaps.current_mut();