Skip to main content

yazi_widgets/input/actor/
kill.rs

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