This commit is contained in:
sxyazi 2023-11-12 06:01:24 +08:00
parent ac4f3a3446
commit ad88b70a4b
No known key found for this signature in database
3 changed files with 116 additions and 26 deletions

View file

@ -14,6 +14,8 @@ impl From<()> for Opt {
impl Input {
pub fn backward(&mut self, _: impl Into<Opt>) -> bool {
return self.move_word(false, false);
let snap = self.snap();
if snap.cursor == 0 {
return self.move_(0);

View file

@ -17,6 +17,7 @@ impl From<bool> for Opt {
impl Input {
pub fn forward(&mut self, opt: impl Into<Opt>) -> 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() {

View file

@ -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<Item = char> + 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<usize>) {
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 {