From ad88b70a4bb9c0e0e94a5c5b0274f6ec7eb841c9 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Sun, 12 Nov 2023 06:01:24 +0800 Subject: [PATCH] .. --- yazi-core/src/input/commands/backward.rs | 2 + yazi-core/src/input/commands/forward.rs | 1 + yazi-core/src/input/input.rs | 139 ++++++++++++++++++----- 3 files changed, 116 insertions(+), 26 deletions(-) diff --git a/yazi-core/src/input/commands/backward.rs b/yazi-core/src/input/commands/backward.rs index 55f88de2..604597fc 100644 --- a/yazi-core/src/input/commands/backward.rs +++ b/yazi-core/src/input/commands/backward.rs @@ -14,6 +14,8 @@ impl From<()> for Opt { impl Input { pub fn backward(&mut self, _: impl Into) -> bool { + return self.move_word(false, false); + let snap = self.snap(); if snap.cursor == 0 { return self.move_(0); diff --git a/yazi-core/src/input/commands/forward.rs b/yazi-core/src/input/commands/forward.rs index 3666dc56..b9cbcece 100644 --- a/yazi-core/src/input/commands/forward.rs +++ b/yazi-core/src/input/commands/forward.rs @@ -17,6 +17,7 @@ impl From for Opt { impl Input { pub fn forward(&mut self, opt: impl Into) -> bool { let opt = opt.into() as Opt; + return self.move_word(false, opt.end_of_word); let snap = self.snap(); if snap.value.is_empty() { diff --git a/yazi-core/src/input/input.rs b/yazi-core/src/input/input.rs index b8d7bd86..b796bbc4 100644 --- a/yazi-core/src/input/input.rs +++ b/yazi-core/src/input/input.rs @@ -1,10 +1,10 @@ -use std::ops::Range; +use std::ops::{Range, RangeBounds}; use crossterm::event::KeyCode; use tokio::sync::mpsc::UnboundedSender; use unicode_width::UnicodeWidthStr; use yazi_config::keymap::Key; -use yazi_shared::InputError; +use yazi_shared::{CharKind, InputError}; use super::{mode::InputMode, op::InputOp, InputOpt, InputSnap, InputSnaps}; use crate::{external, Position}; @@ -45,6 +45,65 @@ impl Input { self.highlight = opt.highlight; } + /// Searches for a word boundary and returns the movement in the cursor + /// position. + /// + /// A word boundary is where the [`CharKind`] changes. However, we also skip + /// initial whitespace. + /// + /// 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, + stop_before_boundary: bool, + ) -> usize { + // Move until we don't see any more whitespace. + let spaces_count = input.clone().take_while(|c| CharKind::new(*c) == CharKind::Space).count(); + let input = input.skip(spaces_count); + + // If we want the *NEXT* end of word, then we want to skip the current + // character. + let mut input = input.skip(stop_before_boundary.into()).peekable(); + + // Determine the current character class. + let prev = input.peek().cloned(); + let Some(prev) = prev else { + return spaces_count; + }; + + // Move until we see a different character class or the end of the iterator. + let character_count = input.take_while(|c| CharKind::new(*c) == CharKind::new(prev)).count(); + + spaces_count + character_count + } + + pub(super) fn move_word(&mut self, forwards: bool, end: bool) -> bool { + let snap = self.snap(); + let idx = snap.idx(snap.cursor).unwrap_or(snap.len()); + + let movement = if forwards { + Self::find_word_boundary(snap.value[idx..].chars(), end) as isize + } else { + -(Self::find_word_boundary(snap.value[..idx].chars().rev(), false) as isize) + }; + self.move_(movement) + } + + fn delete_range(&mut self, range: impl RangeBounds) { + let snap = self.snap_mut(); + snap.cursor = match range.start_bound() { + std::ops::Bound::Included(i) => *i, + std::ops::Bound::Excluded(i) => i + 1, + std::ops::Bound::Unbounded => 0, + }; + snap.value.drain(range); + self.flush_value(); + } + pub fn type_(&mut self, key: &Key) -> bool { if self.mode() != InputMode::Insert { return false; @@ -55,24 +114,67 @@ impl Input { return self.type_str(c.encode_utf8(&mut bits)); } + use KeyCode as K; + use KeyCode::Char as C; + match key { - Key { code: KeyCode::Backspace, shift: false, ctrl: false, alt: false } => self.backspace(), + Key { code: K::Backspace, shift: false, ctrl: false, alt: false } => self.backspace(), // Handle Emacs-style keybindings. - Key { code: KeyCode::Char('a'), shift: false, ctrl: true, alt: false } => { - self.move_(isize::MIN) + Key { code: C('a'), shift: false, ctrl: true, alt: false } => self.move_(isize::MIN), + Key { code: C('e'), shift: false, ctrl: true, alt: false } => self.move_(isize::MAX), + Key { code: C('b'), shift: false, ctrl: true, alt: false } => self.move_(-1), + Key { code: C('f'), shift: false, ctrl: true, alt: false } => self.move_(1), + Key { code: C('h'), shift: false, ctrl: true, alt: false } => self.backspace(), + Key { code: C('d'), shift: false, ctrl: true, alt: false } => self.forward_delete(), + Key { code: C('b'), shift: false, ctrl: false, alt: true } => self.move_word(false, false), + Key { code: C('f'), shift: false, ctrl: false, alt: true } => self.move_word(true, false), + 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); + true } - Key { code: KeyCode::Char('e'), shift: false, ctrl: true, alt: false } => { - self.move_(isize::MAX) + Key { code: C('k'), shift: false, ctrl: true, alt: false } + | Key { code: K::Delete, shift: false, ctrl: false, alt: false } => { + let snap = self.snap_mut(); + let start = snap.idx(snap.cursor).unwrap_or(snap.len()); + self.delete_range(start..); + true } - Key { code: KeyCode::Char('d'), shift: false, ctrl: true, alt: false } => { - self.forward_delete() + Key { code: C('w'), shift: false, ctrl: true, alt: false } + | Key { code: K::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(), false); + self.delete_range(start..end); + true + } + 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(), false); + self.delete_range(start..end); + true } - Key { code: KeyCode::Char('b'), shift: false, ctrl: false, alt: true } => self.backward(()), - Key { code: KeyCode::Char('f'), shift: false, ctrl: false, alt: true } => self.forward(false), _ => false, } } + pub fn forward_delete(&mut self) -> bool { + let snap = self.snaps.current_mut(); + // Return false when there is no character on the right to delete. + // Note that the cursor can be at index `snap.value.len()` when in + // edit mode, but it should be strictly less for forward deletion. + if snap.cursor >= snap.value.len() { + return false; + } else { + snap.value.remove(snap.idx(snap.cursor).unwrap()); + } + self.move_(0); + self.flush_value(); + true + } + pub fn type_str(&mut self, s: &str) -> bool { let snap = self.snaps.current_mut(); if snap.cursor < 1 { @@ -86,21 +188,6 @@ impl Input { true } - pub fn forward_delete(&mut self) -> bool { - let snap = self.snaps.current_mut(); - // Return false when there is no character on the right to delete. - // Note that the cursor can be at index `snap.value.len()` when in - // edit mode. - if snap.cursor > snap.value.len() - 1 { - return false; - } else { - snap.value.remove(snap.idx(snap.cursor).unwrap()); - } - self.move_(0); - self.flush_value(); - true - } - pub fn backspace(&mut self) -> bool { let snap = self.snaps.current_mut(); if snap.cursor < 1 {