yazi_core/input/
input.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use std::ops::Range;

use tokio::sync::mpsc::UnboundedSender;
use unicode_width::UnicodeWidthStr;
use yazi_config::{INPUT, popup::Position};
use yazi_plugin::CLIPBOARD;
use yazi_shared::errors::InputError;

use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};

#[derive(Default)]
pub struct Input {
	pub(super) snaps: InputSnaps,
	pub ticket:       usize,
	pub visible:      bool,

	pub title:    String,
	pub position: Position,

	// Typing
	pub(super) callback:   Option<UnboundedSender<Result<String, InputError>>>,
	pub(super) realtime:   bool,
	pub(super) completion: bool,

	// Shell
	pub highlight: bool,
}

impl Input {
	#[inline]
	pub(super) fn limit(&self) -> usize {
		self.position.offset.width.saturating_sub(INPUT.border()) as usize
	}

	pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
		let old = self.snap().clone();
		let snap = self.snap_mut();

		match snap.op {
			InputOp::None | InputOp::Select(_) => {
				snap.cursor = cursor;
			}
			InputOp::Delete(cut, insert, _) => {
				let range = snap.op.range(cursor, include).unwrap();
				let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);

				let drain = snap.value.drain(start.unwrap()..end.unwrap()).collect::<String>();
				if cut {
					futures::executor::block_on(CLIPBOARD.set(&drain));
				}

				snap.op = InputOp::None;
				snap.mode = if insert { InputMode::Insert } else { InputMode::Normal };
				snap.cursor = range.start;
			}
			InputOp::Yank(_) => {
				let range = snap.op.range(cursor, include).unwrap();
				let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
				let yanked = &snap.value[start.unwrap()..end.unwrap()];

				snap.op = InputOp::None;
				futures::executor::block_on(CLIPBOARD.set(yanked));
			}
		};

		snap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor);
		if snap == &old {
			return false;
		}
		if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
			self.snaps.tag(self.limit()).then(|| self.flush_value());
		}
		true
	}

	pub(super) fn flush_value(&mut self) {
		let Some(tx) = &self.callback else { return };
		self.ticket = self.ticket.wrapping_add(1);

		if self.realtime {
			let value = self.snap().value.clone();
			tx.send(Err(InputError::Typed(value))).ok();
		}

		if self.completion {
			let before = self.partition()[0].to_owned();
			tx.send(Err(InputError::Completed(before, self.ticket))).ok();
		}
	}
}

impl Input {
	#[inline]
	pub fn value(&self) -> &str { self.snap().slice(self.snap().window(self.limit())) }

	#[inline]
	pub fn mode(&self) -> InputMode { self.snap().mode }

	#[inline]
	pub fn cursor(&self) -> u16 {
		let snap = self.snap();
		snap.slice(snap.offset..snap.cursor).width() as u16
	}

	pub fn selected(&self) -> Option<Range<u16>> {
		let snap = self.snap();
		let start = snap.op.start()?;

		let (start, end) =
			if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) };

		let win = snap.window(self.limit());
		let Range { start, end } = start.max(win.start)..end.min(win.end);

		let s = snap.slice(snap.offset..start).width() as u16;
		Some(s..s + snap.slice(start..end).width() as u16)
	}

	#[inline]
	pub fn partition(&self) -> [&str; 2] {
		let snap = self.snap();
		let idx = snap.idx(snap.cursor).unwrap();
		[&snap.value[..idx], &snap.value[idx..]]
	}

	#[inline]
	pub(super) fn snap(&self) -> &InputSnap { self.snaps.current() }

	#[inline]
	pub(super) fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() }
}