yazi_widgets/input/
input.rs1use 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}