yazi_widgets/input/
input.rs1use std::{borrow::Cow, ops::Range};
2
3use crossterm::cursor::SetCursorStyle;
4use yazi_config::YAZI;
5
6use super::{InputSnap, InputSnaps, mode::InputMode, op::InputOp};
7use crate::CLIPBOARD;
8
9pub type InputCallback = Box<dyn Fn(&str, &str)>;
10
11#[derive(Default)]
12pub struct Input {
13 pub snaps: InputSnaps,
14 pub limit: usize,
15 pub obscure: bool,
16 pub callback: Option<InputCallback>,
17}
18
19impl Input {
20 pub fn new(value: String, limit: usize, obscure: bool, callback: InputCallback) -> Self {
21 Self { snaps: InputSnaps::new(value, obscure, limit), limit, obscure, callback: Some(callback) }
22 }
23
24 pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
25 let old = self.snap().clone();
26 let snap = self.snap_mut();
27
28 match snap.op {
29 InputOp::None | InputOp::Select(_) => {
30 snap.cursor = cursor;
31 }
32 InputOp::Delete(cut, insert, _) => {
33 let range = snap.op.range(cursor, include).unwrap();
34 let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
35
36 let drain = snap.value.drain(start.unwrap()..end.unwrap()).collect::<String>();
37 if cut {
38 futures::executor::block_on(CLIPBOARD.set(&drain));
39 }
40
41 snap.op = InputOp::None;
42 snap.mode = if insert { InputMode::Insert } else { InputMode::Normal };
43 snap.cursor = range.start;
44 }
45 InputOp::Yank(_) => {
46 let range = snap.op.range(cursor, include).unwrap();
47 let Range { start, end } = snap.idx(range.start)..snap.idx(range.end);
48 let yanked = &snap.value[start.unwrap()..end.unwrap()];
49
50 snap.op = InputOp::None;
51 futures::executor::block_on(CLIPBOARD.set(yanked));
52 }
53 };
54
55 snap.cursor = snap.count().saturating_sub(snap.mode.delta()).min(snap.cursor);
56 if snap == &old {
57 return false;
58 }
59 if !matches!(old.op, InputOp::None | InputOp::Select(_)) {
60 self.snaps.tag(self.limit).then(|| self.flush_value());
61 }
62 true
63 }
64
65 pub(super) fn flush_value(&mut self) {
66 if let Some(cb) = &self.callback {
67 let (before, after) = self.partition();
68 cb(before, after);
69 }
70 }
71}
72
73impl Input {
74 #[inline]
75 pub fn value(&self) -> &str { &self.snap().value }
76
77 #[inline]
78 pub fn display(&self) -> Cow<'_, str> {
79 if self.obscure {
80 "•".repeat(self.snap().window(self.limit).len()).into()
81 } else {
82 self.snap().slice(self.snap().window(self.limit)).into()
83 }
84 }
85
86 #[inline]
87 pub fn mode(&self) -> InputMode { self.snap().mode }
88
89 #[inline]
90 pub fn cursor(&self) -> u16 { self.snap().width(self.snap().offset..self.snap().cursor) }
91
92 pub fn cursor_shape(&self) -> SetCursorStyle {
93 use InputMode as M;
94
95 match self.mode() {
96 M::Normal if YAZI.input.cursor_blink => SetCursorStyle::BlinkingBlock,
97 M::Normal if !YAZI.input.cursor_blink => SetCursorStyle::SteadyBlock,
98 M::Insert if YAZI.input.cursor_blink => SetCursorStyle::BlinkingBar,
99 M::Insert if !YAZI.input.cursor_blink => SetCursorStyle::SteadyBar,
100 M::Replace if YAZI.input.cursor_blink => SetCursorStyle::BlinkingUnderScore,
101 M::Replace if !YAZI.input.cursor_blink => SetCursorStyle::SteadyUnderScore,
102 M::Normal | M::Insert | M::Replace => unreachable!(),
103 }
104 }
105
106 pub fn selected(&self) -> Option<Range<u16>> {
107 let snap = self.snap();
108 let start = snap.op.start()?;
109
110 let (start, end) =
111 if start < snap.cursor { (start, snap.cursor) } else { (snap.cursor + 1, start + 1) };
112
113 let win = snap.window(self.limit);
114 let Range { start, end } = start.max(win.start)..end.min(win.end);
115
116 let s = snap.width(snap.offset..start);
117 Some(s..s + snap.width(start..end))
118 }
119
120 #[inline]
121 pub fn partition(&self) -> (&str, &str) {
122 let snap = self.snap();
123 let idx = snap.idx(snap.cursor).unwrap();
124 (&snap.value[..idx], &snap.value[idx..])
125 }
126
127 #[inline]
128 pub fn snap(&self) -> &InputSnap { self.snaps.current() }
129
130 #[inline]
131 pub fn snap_mut(&mut self) -> &mut InputSnap { self.snaps.current_mut() }
132}