mudrs_milk/widget/
input.rs

1use 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}