yazi_widgets/input/commands/
kill.rs

1use std::ops::RangeBounds;
2
3use anyhow::Result;
4use yazi_macro::{act, render, succ};
5use yazi_parser::input::KillOpt;
6use yazi_shared::{CharKind, data::Data};
7
8use crate::input::Input;
9
10impl Input {
11	pub fn kill(&mut self, opt: KillOpt) -> Result<Data> {
12		let snap = self.snap_mut();
13		match opt.kind.as_ref() {
14			"all" => self.kill_range(..),
15			"bol" => {
16				let end = snap.idx(snap.cursor).unwrap_or(snap.len());
17				self.kill_range(..end)
18			}
19			"eol" => {
20				let start = snap.idx(snap.cursor).unwrap_or(snap.len());
21				self.kill_range(start..)
22			}
23			"backward" => {
24				let end = snap.idx(snap.cursor).unwrap_or(snap.len());
25				let start = end - Self::find_word_boundary(snap.value[..end].chars().rev());
26				self.kill_range(start..end)
27			}
28			"forward" => {
29				let start = snap.idx(snap.cursor).unwrap_or(snap.len());
30				let end = start + Self::find_word_boundary(snap.value[start..].chars());
31				self.kill_range(start..end)
32			}
33			_ => succ!(),
34		}
35	}
36
37	fn kill_range(&mut self, range: impl RangeBounds<usize>) -> Result<Data> {
38		let snap = self.snap_mut();
39		snap.cursor = match range.start_bound() {
40			std::ops::Bound::Included(i) => *i,
41			std::ops::Bound::Excluded(_) => unreachable!(),
42			std::ops::Bound::Unbounded => 0,
43		};
44		if snap.value.drain(range).next().is_none() {
45			succ!();
46		}
47
48		act!(r#move, self)?;
49		self.flush_value();
50		succ!(render!());
51	}
52
53	/// Searches for a word boundary and returns the movement in the cursor
54	/// position.
55	///
56	/// A word boundary is where the [`CharKind`] changes.
57	///
58	/// If `skip_whitespace_first` is true, we skip initial whitespace.
59	/// Otherwise, we skip whitespace after reaching a word boundary.
60	///
61	/// If `stop_before_boundary` is true, returns how many characters the cursor
62	/// needs to move to be at the character *BEFORE* the word boundary, or until
63	/// the end of the iterator.
64	///
65	/// Otherwise, returns how many characters to move to reach right *AFTER* the
66	/// word boundary, or the end of the iterator.
67	fn find_word_boundary(input: impl Iterator<Item = char> + Clone) -> usize {
68		fn count_spaces(input: impl Iterator<Item = char>) -> usize {
69			// Move until we don't see any more whitespace.
70			input.take_while(|&c| CharKind::new(c) == CharKind::Space).count()
71		}
72
73		fn count_characters(mut input: impl Iterator<Item = char>) -> usize {
74			// Determine the current character class.
75			let first = match input.next() {
76				Some(c) => CharKind::new(c),
77				None => return 0,
78			};
79
80			// Move until we see a different character class or the end of the iterator.
81			input.take_while(|&c| CharKind::new(c) == first).count() + 1
82		}
83
84		let n = count_spaces(input.clone());
85		let n = n + count_characters(input.clone().skip(n));
86		input.take(n).fold(0, |acc, c| acc + c.len_utf8())
87	}
88}