Skip to main content

yazi_widgets/input/
input.rs

1use std::{borrow::Cow, ops::Range};
2
3use anyhow::Result;
4use crossterm::cursor::SetCursorStyle;
5use tokio::sync::mpsc;
6use yazi_config::YAZI;
7use yazi_macro::act;
8use yazi_shared::Ids;
9
10use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};
11use crate::{CLIPBOARD, input::{InputEvent, InputOpt, SEPARATOR}};
12
13#[derive(Default)]
14pub struct Input {
15	pub snaps:      InputSnaps,
16	pub limit:      usize,
17	pub obscure:    bool,
18	pub realtime:   bool,
19	pub completion: bool,
20
21	pub tx:     Option<mpsc::UnboundedSender<InputEvent>>,
22	pub ticket: Ids,
23}
24
25impl Input {
26	pub fn new(opt: InputOpt) -> Result<Self> {
27		let limit = opt.cfg.position.offset.width.saturating_sub(YAZI.input.border()) as usize;
28		let mut input = Self {
29			snaps: InputSnaps::new(opt.cfg.value, opt.cfg.obscure, limit),
30			limit,
31			obscure: opt.cfg.obscure,
32			realtime: opt.cfg.realtime,
33			completion: opt.cfg.completion,
34
35			tx: Some(opt.tx),
36			..Default::default()
37		};
38
39		if let Some(cursor) = opt.cfg.cursor {
40			input.snap_mut().cursor = cursor;
41			act!(r#move, input)?;
42		}
43
44		Ok(input)
45	}
46
47	pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
48		let old = self.snap().clone();
49		let snap = self.snap_mut();
50
51		match snap.op {
52			InputOp::None | InputOp::Select(_) => {
53				snap.cursor = cursor;
54			}
55			InputOp::Delete(cut, insert, _) => {
56				let range = snap.op.range(cursor, include).unwrap();
57				let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
58
59				let drain = snap.value.drain(start.unwrap()..end.unwrap()).collect::<String>();
60				if cut {
61					futures::executor::block_on(CLIPBOARD.set(&drain));
62				}
63
64				snap.op = InputOp::None;
65				snap.mode = if insert { InputMode::Insert } else { InputMode::Normal };
66				snap.cursor = range.start;
67			}
68			InputOp::Yank(_) => {
69				let range = snap.op.range(cursor, include).unwrap();
70				let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
71				let yanked = &snap.value[start.unwrap()..end.unwrap()];
72
73				snap.op = InputOp::None;
74				futures::executor::block_on(CLIPBOARD.set(yanked));
75			}
76		};
77
78		snap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor);
79		if snap == &old {
80			return false;
81		}
82		if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
83			self.snaps.tag(self.limit).then(|| self.flush_type());
84		}
85		true
86	}
87
88	pub(super) fn flush_type(&mut self) {
89		self.ticket.next();
90		if let Some(tx) = self.tx.as_ref().filter(|_| self.realtime) {
91			tx.send(InputEvent::Type(self.value().to_owned())).ok();
92		}
93
94		self.flush_trigger(true);
95	}
96
97	pub(super) fn flush_trigger(&self, force: bool) {
98		if let Some(tx) = self.tx.as_ref().filter(|_| self.completion) {
99			tx.send(InputEvent::Trigger(
100				self.partition().0.to_owned(),
101				Some(self.ticket.current()).filter(|_| !force),
102			))
103			.ok();
104		}
105	}
106}
107
108impl Input {
109	pub fn value(&self) -> &str { &self.snap().value }
110
111	pub fn display(&self) -> Cow<'_, str> {
112		if self.obscure {
113			"•".repeat(self.snap().window(self.limit).len()).into()
114		} else {
115			self.snap().slice(self.snap().window(self.limit)).into()
116		}
117	}
118
119	pub fn mode(&self) -> InputMode { self.snap().mode }
120
121	pub fn cursor(&self) -> u16 { self.snap().width(self.snap().offset..self.snap().cursor) }
122
123	pub fn cursor_shape(&self) -> SetCursorStyle {
124		use InputMode as M;
125
126		match self.mode() {
127			M::Normal if YAZI.input.cursor_blink => SetCursorStyle::BlinkingBlock,
128			M::Normal if !YAZI.input.cursor_blink => SetCursorStyle::SteadyBlock,
129			M::Insert if YAZI.input.cursor_blink => SetCursorStyle::BlinkingBar,
130			M::Insert if !YAZI.input.cursor_blink => SetCursorStyle::SteadyBar,
131			M::Replace if YAZI.input.cursor_blink => SetCursorStyle::BlinkingUnderScore,
132			M::Replace if !YAZI.input.cursor_blink => SetCursorStyle::SteadyUnderScore,
133			M::Normal | M::Insert | M::Replace => unreachable!(),
134		}
135	}
136
137	pub fn selected(&self) -> Option<Range<u16>> {
138		let snap = self.snap();
139		let start = snap.op.start()?;
140
141		let (start, end) =
142			if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) };
143
144		let win = snap.window(self.limit);
145		let Range { start, end } = start.max(win.start)..end.min(win.end);
146
147		let s = snap.width(snap.offset..start);
148		Some(s..s + snap.width(start..end))
149	}
150
151	pub fn partition(&self) -> (&str, &str) {
152		let snap = self.snap();
153		let idx = snap.idx(snap.cursor).unwrap();
154
155		if let Some(sep) = snap.value[idx..].find(SEPARATOR).map(|i| idx + i) {
156			(&snap.value[..sep], &snap.value[sep + 1..])
157		} else {
158			(&snap.value, "")
159		}
160	}
161
162	pub fn snap(&self) -> &InputSnap { self.snaps.current() }
163
164	pub fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() }
165}