mudrs_milk/widget/
input.rs1use std::{
2 iter::FromIterator,
3 mem::replace,
4 time::Instant,
5};
6
7use crossterm::event::{
8 KeyCode,
9 KeyEvent,
10 KeyModifiers,
11};
12
13use futures::{
14 channel::mpsc,
15 prelude::*,
16};
17use ringbuffer::{
18 AllocRingBuffer,
19 RingBuffer,
20 RingBufferExt,
21 RingBufferWrite,
22};
23use textwrap::core::{
24 Fragment,
25 Word,
26};
27use tracing::debug;
28use tui::{
29 buffer::Buffer,
30 layout::Rect,
31 widgets::Widget,
32};
33use unicode_segmentation::UnicodeSegmentation;
34use textwrap::word_separators::WordSeparator;
35
36use crate::{
37 script::Flags,
38};
39
40use crossterm::event::Event;
41
42fn find_words<'a>(line: &'a str) -> Box<dyn Iterator<Item = Word<'a>> + 'a> {
43 textwrap::word_separators::AsciiSpace::default().find_words(line)
44}
45
46pub struct InputBuffer {
47 buffer: String,
48
49 history: AllocRingBuffer<String>,
50 history_selection: Option<isize>,
51
52 searching: bool,
53
54 prompt: String,
55 cursor: usize,
56 flags: Flags,
57 output_tx: mpsc::Sender<String>,
58 render_tx: mpsc::Sender<Instant>,
59}
60
61impl InputBuffer {
62 pub fn new(
63 flags: Flags,
64 output_tx: mpsc::Sender<String>,
65 render_tx: mpsc::Sender<Instant>,
66 ) -> Self {
67 InputBuffer {
68 buffer: String::new(),
69 cursor: 0,
70
71 history: AllocRingBuffer::new(),
72 history_selection: None,
73 searching: false,
74
75 prompt: "> ".into(),
76
77 flags,
78 output_tx,
79 render_tx,
80 }
81 }
82
83 pub fn is_empty(&self) -> bool {
84 self.buffer.is_empty()
85 }
86
87 pub fn insert(&mut self, c: char) {
88 if self.cursor == self.buffer.len() {
89 self.buffer.push(c)
90 } else {
91 self.buffer.insert(self.cursor, c)
92 }
93 self.cursor += 1;
94 if self.searching {
95 self.search_next();
96 } else {
97 self.history_selection = None;
98 }
99 }
100
101 pub fn previous(&mut self) {
102 self.searching = false;
103 let i = self.history_selection.unwrap_or(0) - 1;
104 if let Some(cmd) = self.history.get(i) {
105 self.buffer = cmd.clone();
106 self.history_selection = Some(i);
107 self.cursor = self.buffer.len();
108 }
109 }
110
111 pub fn next(&mut self) {
112 self.searching = false;
113 let i = self.history_selection.take().unwrap_or(0) + 1;
114 if i >= 0 {
115 return;
116 }
117 if let Some(cmd) = self.history.get(i) {
118 self.buffer = cmd.clone();
119 self.history_selection = Some(i);
120 self.cursor = self.buffer.len();
121 }
122 }
123
124 pub fn search_next(&mut self) {
125 for i in self.history_selection.unwrap_or(0)..(-1 * (self.history.len() as isize)) {
126 if i == 0 {
127 continue;
128 }
129
130 let entry = match self.history.get(i) {
131 Some(entry) => entry,
132 None => {
133 self.history_selection = None;
134 return;
135 }
136 };
137
138 if entry.starts_with(&self.buffer) {
139 self.history_selection = Some(i);
140 return;
141 }
142 }
143 }
144
145 pub fn start_search(&mut self) {
146 self.searching = true;
147 self.history_selection = Some(0);
148 self.buffer.clear();
149 self.cursor = 0;
150 }
151
152 pub fn wrapped(&self, len: usize) -> InputBufferWrapped {
153 let mut full_input = self.prompt.clone();
154
155 if self.searching {
156 full_input.push_str("(search) [");
157 full_input.push_str(&self.buffer);
158 full_input.push_str(" ] ");
159 } else if let Some(i) = self.history_selection {
160 if let Some(entry) = self.history.get(i) {
161 full_input.push_str(entry);
162 }
163 } else {
164 full_input.push_str(&self.buffer);
165 }
166
167 let line_count = full_input.lines().count();
168 let mut lines = Vec::with_capacity(line_count);
169 for line in full_input
170 .lines()
171 .map(find_words)
172 .map(Vec::from_iter)
173 {
174 let wrapped = textwrap::wrap_algorithms::wrap_first_fit(&line, &[len; 512]);
175 wrapped
176 .into_iter()
177 .map(|l| {
178 l.iter()
179 .fold(String::with_capacity(l.len() * 5), |mut acc, w| {
180 acc.push_str(&w);
181 for _ in 0..w.whitespace_width() {
182 acc.push(' ');
183 }
184 acc
185 })
186 })
187 .for_each(|l| lines.push(l));
188 }
189
190 let mut prompt_len = self
191 .prompt
192 .graphemes(true)
193 .filter(|g| !g.contains('\n') && !g.contains('\r'))
194 .count();
195
196 if self.searching {
197 prompt_len += "(search) [".len();
198 }
199
200 debug!(?lines, "input lines");
201
202 InputBufferWrapped {
203 lines,
204 prompt_len,
205 flags: &self.flags,
206 cursor: self.cursor,
207 }
208 }
209
210 pub fn clear(&mut self) {
211 self.searching = false;
212 self.history_selection = None;
213 self.buffer.clear();
214 self.cursor = 0;
215 }
216
217 pub fn accept(&mut self) -> String {
218 let maybe_cmd = if self.searching {
219 self.searching = false;
220 let selection = self.history_selection.take();
221 selection.and_then(|i| self.history.get(i))
222 } else {
223 None
224 };
225
226 let cmd = match maybe_cmd {
227 Some(cmd) => cmd.clone(),
228 None => {
229 let len = self.buffer.len();
230 self.cursor = 0;
231 replace(&mut self.buffer, String::with_capacity(len))
232 }
233 };
234
235 tracing::debug!(?cmd, "Accepted cmd");
236 self.clear();
237 if !self.flags.noecho() {
238 self.history.push(cmd.clone());
239 }
240 cmd
241 }
242
243 pub async fn handle_user_event(&mut self, event: &Event) -> bool {
244 match event {
245 Event::Key(KeyEvent {
246 code: key,
247 modifiers: KeyModifiers::NONE,
248 }) => match key {
249 KeyCode::Enter => {
250 let cmd = self.accept();
251 if let Err(err) = self.output_tx.send(cmd).await {
252 tracing::error!(?err, "error sending input line");
253 };
254 debug!("sent command");
255 }
256 KeyCode::Backspace => {
257 self.backspace();
258 }
259 KeyCode::Up => {
260 self.previous();
261 }
262 KeyCode::Down => {
263 self.next();
264 }
265 KeyCode::Char(c) => {
266 self.insert(*c);
267 }
268 _ => return false,
269 },
270 Event::Key(KeyEvent {
271 code: key,
272 modifiers: KeyModifiers::CONTROL,
273 }) => match key {
274 KeyCode::Char('d') => {
275 if self.is_empty() {
276 self.flags.exit();
277 } else {
278 self.delete();
279 }
280 }
281 KeyCode::Char('a') => {
282 self.cursor_home();
283 }
284 KeyCode::Char('e') => {
285 self.cursor_end();
286 }
287 KeyCode::Char('b') => {
288 self.cursor_back();
289 }
290 KeyCode::Char('f') => {
291 self.cursor_forward();
292 }
293 KeyCode::Char('c') => {
294 self.clear();
295 }
296 KeyCode::Char('k') => {
297 self.delete_forward();
298 }
299 KeyCode::Char('r') => {
300 if self.searching {
301 self.search_next();
302 } else {
303 self.start_search();
304 }
305 }
306 _ => return false,
307 },
308 Event::Key(KeyEvent {
309 code: key,
310 modifiers: KeyModifiers::ALT,
311 }) => match key {
312 KeyCode::Char('f') => {
313 self.cursor_fword();
314 }
315 KeyCode::Char('b') => {
316 self.cursor_bword();
317 }
318 KeyCode::Char('\u{7f}') => {
319 self.backspace_word();
320 }
321 _ => return false,
322 },
323 Event::Key(KeyEvent {
324 code: KeyCode::Char(c),
325 modifiers,
326 }) => {
327 let mut c = *c;
328 if *modifiers == KeyModifiers::SHIFT {
329 c = c.to_ascii_uppercase();
330 }
331 self.insert(c);
332 }
333 _ => return false,
334 }
335 let _ = self.render_tx.send(Instant::now()).await;
336 true
337 }
338
339 pub fn backspace(&mut self) {
340 if self.cursor > 0 {
341 self.buffer.remove(self.cursor - 1);
342 }
343 self.cursor_back();
344 }
345
346 pub fn backspace_word(&mut self) {
347 let start = self.find_word_bwd();
348 self.buffer.drain(start..self.cursor);
349 self.cursor = start;
350 }
351
352 pub fn delete(&mut self) {
353 if self.buffer.len() > 0 && self.cursor < self.buffer.len() {
354 self.buffer.remove(self.cursor);
355 }
356 }
357
358 pub fn cursor_end(&mut self) {
359 self.cursor = self.buffer.len();
360 }
361
362 pub fn cursor_home(&mut self) {
363 self.cursor = 0;
364 }
365
366 pub fn cursor_back(&mut self) {
367 self.cursor = self.cursor.saturating_sub(1);
368 }
369
370 pub fn cursor_forward(&mut self) {
371 self.cursor += 1;
372 if self.cursor > self.buffer.len() {
373 self.cursor = self.buffer.len();
374 }
375 }
376
377 pub fn cursor_bword(&mut self) {
378 self.cursor = self.find_word_bwd();
379 }
380
381 pub fn cursor_fword(&mut self) {
382 self.cursor = self.find_word_fwd();
383 }
384
385 pub fn delete_forward(&mut self) {
386 if self.cursor < self.buffer.len() {
387 self.buffer.drain(self.cursor..);
388 }
389 }
390
391 fn find_word_bwd(&self) -> usize {
392 let mut cursor = self.cursor;
393 let mut chars = self
394 .buffer
395 .chars()
396 .rev()
397 .skip(self.buffer.len() - self.cursor);
398 for char in &mut chars {
399 cursor -= 1;
400 if char.is_alphanumeric() {
401 break;
402 }
403 }
404 for char in &mut chars {
405 cursor -= 1;
406 if !char.is_alphanumeric() {
407 return cursor + 1;
408 }
409 }
410 return 0;
411 }
412 fn find_word_fwd(&self) -> usize {
413 let mut chars = self.buffer.chars().enumerate().skip(self.cursor);
414 for (_, char) in &mut chars {
415 if char.is_alphanumeric() {
416 break;
417 }
418 }
419 for (cursor, char) in &mut chars {
420 if !char.is_alphanumeric() {
421 return cursor;
422 }
423 }
424 return self.buffer.len();
425 }
426}
427
428pub struct InputBufferWrapped<'a> {
429 lines: Vec<String>,
430 prompt_len: usize,
431 cursor: usize,
432 flags: &'a Flags,
433}
434
435impl<'a> InputBufferWrapped<'a> {
436 pub fn len(&self) -> usize {
437 self.lines.len()
438 }
439
440 pub fn cursor_pos(&self) -> (usize, usize) {
441 let mut pos: usize = 0;
442 let mut target = self.prompt_len;
443 if !self.flags.noecho() {
444 target += self.cursor;
445 }
446 let mut x = 0;
447 let mut y = 0;
448 'outer: for line in &self.lines {
449 if pos >= target {
450 break 'outer;
451 }
452 for _ in line.graphemes(true) {
453 if pos >= target {
454 break 'outer;
455 }
456 pos += 1;
457 x += 1;
458 }
459 if pos >= target {
460 break;
461 }
462 y += 1;
463 x = 0;
464 }
465
466 (x, y)
467 }
468}
469
470impl<'a> Widget for InputBufferWrapped<'a> {
471 fn render(self, area: Rect, buf: &mut Buffer) {
472 let mut count: usize = 0;
473 for (y, line) in self.lines.into_iter().enumerate() {
474 let mut x = 0;
475 for grapheme in line.graphemes(true) {
476 if grapheme.contains('\r') || grapheme.contains('\n') {
477 continue;
478 }
479 if self.flags.noecho() && count >= self.prompt_len {
480 return;
481 }
482 buf.get_mut(area.x + x as u16, area.y + y as u16)
483 .set_symbol(grapheme);
484 x += 1;
485 count += 1;
486 }
487 }
488 }
489}