ttyui/
readline.rs

1//! An alternative readline implementation.
2//!
3//! This module realizes traditional line editor implementation with Emacs-like shortcuts.
4//!
5//! ```rust
6//! use std::env;
7//! use ttyui::readline::Buffer;
8//!
9//! let mut buf = Buffer::new();
10//! if let Some(x) = env::args().nth(1) {
11//!     if x == "-d" || x == "--double" {
12//!         buf.double_line_response = true;
13//!     } else {
14//!         panic!("unknown arguments");
15//!     }
16//! }
17//! buf.read_line().unwrap();
18//! println!(
19//!     "\n\n[output]\n\x1b[33m{}\x1b[0m",
20//!     buf.to_string(),
21//! );
22//!
23//! ```
24//!
25
26use console::{Key, Term};
27use std::io;
28use std::io::Write;
29
30const MAX_PREFIX_CAPACITY: usize = 32;
31const DEFAULT_TEXT_CAPACITY: usize = 1024;
32
33/// Buffer of a readline instance.
34///
35pub struct Buffer {
36    /// Debug mode or not.
37    debug: bool,
38    /// Whether the read_line method result newline-containing string at the index where an enter key has been pressed.
39    pub double_line_response: bool,
40    /// Whether the read_line method self.terminates or not when the ArrowUp or ArrowDown key is pressed.
41    pub terminate_on_up_down: bool,
42    term: Term,
43    /// Cursor index for the next character input
44    index: usize,
45    /// prefix string for the input area
46    prefix: String,
47    /// Text payload for the buffer
48    text: String,
49}
50
51impl ToString for Buffer {
52    fn to_string(&self) -> String {
53        self.text.clone()
54    }
55}
56
57impl Drop for Buffer {
58    fn drop(&mut self) {
59        self.double_line_response = false;
60        self.index = 0;
61        self.text.clear();
62    }
63}
64
65impl std::fmt::Debug for Buffer {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        f.debug_struct("Buffer")
68            .field("debug", &self.debug)
69            .field("double_line_response", &self.double_line_response)
70            .field("terminate_on_up_down", &self.terminate_on_up_down)
71            .field("index", &self.index)
72            .field("text", &self.text);
73        Ok(())
74    }
75}
76
77impl Clone for Buffer {
78    fn clone(&self) -> Self {
79        Self {
80            debug: self.debug,
81            double_line_response: self.double_line_response,
82            terminate_on_up_down: self.terminate_on_up_down,
83            term: self.term.clone(),
84            index: self.index,
85            prefix: self.prefix.clone(),
86            text: self.text.clone(),
87        }
88    }
89}
90
91impl Buffer {
92    /// Generate blank buffer.
93    ///
94    pub fn new() -> Self {
95        Buffer {
96            debug: false,
97            double_line_response: false,
98            terminate_on_up_down: false,
99            term: Term::stdout(),
100            index: 0,
101            prefix: String::with_capacity(MAX_PREFIX_CAPACITY),
102            text: String::with_capacity(DEFAULT_TEXT_CAPACITY),
103        }
104    }
105
106    /// Generate buffer with initial text.
107    ///
108    pub fn from(text: &str) -> Self {
109        Buffer {
110            debug: false,
111            double_line_response: false,
112            terminate_on_up_down: false,
113            term: Term::stdout(),
114            index: 0,
115            prefix: String::with_capacity(MAX_PREFIX_CAPACITY),
116            text: String::from(text),
117        }
118    }
119
120    /// Switch on debug mode
121    ///
122    pub fn debug(&mut self) {
123        self.debug = true;
124    }
125
126    fn enter(&mut self) -> io::Result<Key> {
127        if self.double_line_response {
128            self.text.insert(self.index, '\n');
129        }
130        Ok(Key::Enter)
131    }
132    fn home(&mut self) -> io::Result<Key> {
133        self.term.move_cursor_left(self.index)?;
134        self.index = 0;
135        Ok(Key::Home)
136    }
137    fn end(&mut self) -> io::Result<Key> {
138        self.term.move_cursor_right(self.text.len() - self.index)?;
139        self.index = self.text.len();
140        Ok(Key::End)
141    }
142    fn char(&mut self, x: char) -> io::Result<Key> {
143        self.term.move_cursor_right(self.text.len() - self.index)?;
144        self.term.clear_chars(self.text.len() - self.index)?;
145        self.text.insert(self.index, x);
146        write!(&self.term, "{}", &self.text[self.index..self.text.len()])?;
147        self.index += 1;
148        self.term.move_cursor_left(self.text.len() - self.index)?;
149        Ok(Key::Char(x))
150    }
151    fn backspace(&mut self) -> io::Result<Key> {
152        if self.index > 0 {
153            self.term.clear_line()?;
154            self.term
155                .move_cursor_left(self.text.len() + self.prefix.len())?;
156            write!(&self.term, "{}", self.prefix)?;
157            self.text.remove(self.index - 1);
158            self.index -= 1;
159            write!(&self.term, "{}", self.text)?;
160            self.term.move_cursor_left(self.text.len() - self.index)?;
161        }
162        Ok(Key::Backspace)
163    }
164    fn del(&mut self) -> io::Result<Key> {
165        if self.text.len() > 0 && self.text.len() > self.index {
166            self.text.remove(self.index);
167            self.term.clear_line()?;
168            write!(&self.term, "{}", self.text)?;
169            self.term.move_cursor_left(self.text.len() - self.index)?;
170        }
171        Ok(Key::Del)
172    }
173    fn esc(&mut self) -> io::Result<()> {
174        match self.term.read_key()? {
175            Key::Char('f') => {
176                self.word_forward()?;
177            }
178            Key::Char('b') => {
179                self.word_backword()?;
180            }
181            Key::Char('d') => {
182                self.word_delete()?;
183            }
184            Key::Backspace => {
185                self.word_backspace()?;
186            }
187            _ => {}
188        }
189        Ok(())
190    }
191
192    fn word_forward(&mut self) -> io::Result<()> {
193        let mut separater_ids = self
194            .text
195            .match_indices(' ')
196            .map(|t| t.0)
197            .collect::<Vec<usize>>();
198        separater_ids.push(self.text.len());
199        for i in separater_ids {
200            if i > self.index {
201                self.term.move_cursor_right(i - self.index)?;
202                self.index = i;
203                break;
204            }
205        }
206        Ok(())
207    }
208
209    fn word_backword(&mut self) -> io::Result<()> {
210        let mut separater_ids = self
211            .text
212            .match_indices(' ')
213            .map(|t| t.0 + 1)
214            .collect::<Vec<usize>>();
215        separater_ids.insert(0, 0);
216        separater_ids.reverse();
217        for i in separater_ids {
218            if self.index > i {
219                self.term.move_cursor_left(self.index - i)?;
220                self.index = i;
221                break;
222            }
223        }
224        Ok(())
225    }
226
227    fn word_backspace(&mut self) -> io::Result<()> {
228        let mut separater_ids = self
229            .text
230            .match_indices(' ')
231            .map(|t| t.0)
232            .filter(|n| *n < self.index)
233            .collect::<Vec<usize>>();
234        separater_ids.insert(0, 0);
235        separater_ids.reverse();
236
237        if self.text.len() != 0 {
238            let target_id = separater_ids[0];
239            let new_text =
240                self.text[0..target_id].to_string() + &self.text[self.index..self.text.len()];
241            self.text.clear();
242            self.text = new_text;
243            self.index = target_id;
244            self.term.clear_line()?;
245            write!(&self.term, "{}", self.text)?;
246            self.term.move_cursor_left(self.text.len() - target_id)?;
247        }
248
249        Ok(())
250    }
251
252    fn word_delete(&mut self) -> io::Result<()> {
253        let mut separater_ids = self
254            .text
255            .match_indices(' ')
256            .map(|t| t.0)
257            .filter(|n| *n > self.index)
258            .collect::<Vec<usize>>();
259        separater_ids.push(self.text.len());
260        let target_id = separater_ids[0];
261        let new_text =
262            self.text[0..self.index].to_string() + &self.text[target_id..self.text.len()];
263        self.text.clear();
264        self.text = new_text;
265        self.term.clear_line()?;
266        write!(&self.term, "{}", self.text)?;
267        self.term.move_cursor_left(self.text.len() - self.index)?;
268        Ok(())
269    }
270
271    fn left(&mut self) -> io::Result<Key> {
272        if self.index > 0 {
273            self.term.move_cursor_left(1)?;
274            self.index -= 1;
275        }
276        Ok(Key::ArrowLeft)
277    }
278    fn right(&mut self) -> io::Result<Key> {
279        if self.index < self.text.len() {
280            self.term.move_cursor_right(1)?;
281            self.index += 1;
282        }
283        Ok(Key::ArrowRight)
284    }
285
286    /// set prefix for the input area
287    pub fn set_prefix(&mut self, prefix: String) {
288        self.prefix.clear();
289        self.prefix = prefix;
290    }
291
292    ///Buffer.read_line provides interactive line editing functionality for a tty, which supports following basic shortcut keys:
293    ///
294    /// * C-a (Home)
295    /// * C-e (End)
296    /// * M-d (word delete)
297    /// * M-f (word forward)
298    /// * M-b (word backward)
299    ///
300    pub fn read_line(&mut self) -> io::Result<Key> {
301        let k: Key;
302
303        write!(&self.term, "{}", self.prefix)?;
304        loop {
305            match self.term.read_key()? {
306                Key::Enter => {
307                    k = self.enter()?;
308                    break;
309                }
310                Key::Home => {
311                    self.home()?;
312                }
313                Key::End => {
314                    self.end()?;
315                }
316                Key::ArrowRight => {
317                    self.right()?;
318                }
319                Key::ArrowLeft => {
320                    self.left()?;
321                }
322                Key::Backspace => {
323                    self.backspace()?;
324                }
325                Key::Del => {
326                    self.del()?;
327                }
328                Key::Char(x) => {
329                    self.char(x)?;
330                }
331                Key::Escape => {
332                    self.esc()?;
333                }
334                Key::ArrowUp => {
335                    if self.terminate_on_up_down {
336                        k = Key::ArrowUp;
337                        break;
338                    }
339                }
340                Key::ArrowDown => {
341                    if self.terminate_on_up_down {
342                        k = Key::ArrowDown;
343                        break;
344                    }
345                }
346                _ => {}
347            }
348        }
349        Ok(k)
350    }
351}
352
353/// A shortcut to Buffer.read_line()?.to_string.
354///
355/// Its response contains no newline.
356///
357/// ```rust
358/// use ttyui::readline::read_line;
359/// println!("\n\n[output]\n\x1b[33m{}\x1b[0m", read_line().unwrap());
360/// ```
361///
362pub fn read_line() -> io::Result<String> {
363    let mut buf = Buffer::new();
364    buf.read_line()?;
365    Ok(buf.to_string())
366}
367
368/// A shortcut to Buffer.read_line()?.to_string, but returns double line (which contains newline character in the response).
369///
370/// ```rust
371/// use ttyui::readline::read_line2;
372/// println!("\n\n[output]\n\x1b[33m{}\x1b[0m", read_line2().unwrap());
373/// ```
374///
375pub fn read_line2() -> io::Result<String> {
376    let mut buf = Buffer::new();
377    buf.double_line_response = true;
378    buf.read_line()?;
379    Ok(buf.to_string())
380}
381
382#[cfg(test)]
383mod tests {
384    use crate::readline::*;
385
386    const DUMMY_TEXT: &str = "okachimachi koshigaya inogashira suidobashi ochanomidzu";
387    const DUMMY_INDEX: usize = 19;
388
389    fn init_with_word() -> Buffer {
390        let mut buf = Buffer::new();
391        buf.text = "kabukiza".to_string();
392        buf
393    }
394
395    fn init_modifying_buffer() -> Buffer {
396        let mut buf = Buffer::new();
397        buf.text = DUMMY_TEXT.to_string();
398        buf.index = DUMMY_INDEX;
399        buf
400    }
401
402    #[test]
403    fn test_new() {
404        let b = init_modifying_buffer();
405        assert!(!b.debug);
406        assert!(!b.double_line_response);
407    }
408
409    #[test]
410    fn test_home() {
411        let mut b = init_modifying_buffer();
412        let h = b.home().unwrap();
413        assert_eq!(h, Key::Home);
414        assert_eq!(b.index, 0);
415    }
416
417    #[test]
418    fn test_end() {
419        let mut b = init_modifying_buffer();
420        let k = b.end().unwrap();
421        assert_eq!(k, Key::End);
422        assert_eq!(b.index, b.text.len());
423    }
424
425    #[test]
426    fn test_char_input_at_start_results_a_char() {
427        let mut b = Buffer::new();
428        let k = b.char('g').unwrap();
429        assert_eq!(k, Key::Char('g'));
430        assert_eq!(b.index, 1);
431        assert_eq!(b.text, "g".to_string());
432    }
433
434    #[test]
435    fn test_char_input_before_word_results_inserted_char() {
436        let mut b = init_with_word();
437        let mut text_swap = b.text.clone();
438        let k = b.char('@').unwrap();
439        assert_eq!(k, Key::Char('@'));
440        assert_eq!(b.index, 1);
441        text_swap.insert(0, '@');
442        assert_eq!(b.text, text_swap);
443    }
444
445    #[test]
446    fn test_char_input_between_characters_inserted_char() {
447        let mut b = init_modifying_buffer();
448        let idx_init = b.index;
449        let mut text_swap = b.text.clone();
450        let k = b.char('g').unwrap();
451        assert_eq!(k, Key::Char('g'));
452        assert_eq!(b.index, idx_init + 1);
453        text_swap.insert(idx_init, 'g');
454        assert_eq!(b.text, text_swap);
455    }
456
457    #[test]
458    fn test_string_input_results_modified_word() {
459        let mut b = init_modifying_buffer();
460        let idx_init = b.index;
461        let mut text_swap = b.text.clone();
462        b.char('i').unwrap();
463        b.char('t').unwrap();
464        b.char('a').unwrap();
465        b.char('i').unwrap();
466        assert_eq!(b.index, idx_init + "itai".len());
467        text_swap.insert_str(idx_init, "itai");
468        assert_eq!(
469            b.text,
470            "okachimachi koshigaitaiya inogashira suidobashi ochanomidzu".to_string()
471        );
472    }
473
474    #[test]
475    fn test_backspace_after_characters_removes_char() {
476        let mut b = init_modifying_buffer();
477        let idx_init = b.index;
478        let mut text_swap = b.text.clone();
479        b.backspace().unwrap();
480        assert_eq!(b.index, idx_init - 1);
481        text_swap.remove(idx_init - 1);
482        assert_eq!(b.text, text_swap);
483    }
484
485    #[test]
486    fn test_backspace_before_characters_has_no_effect() {
487        let mut b = init_with_word();
488        let idx_init = b.index;
489        let text_swap = b.text.clone();
490        b.backspace().unwrap();
491        assert_eq!(b.index, idx_init);
492        assert_eq!(b.text, text_swap);
493    }
494
495    #[test]
496    fn test_delete_before_characters_results_shortened_string() {
497        let mut b = init_with_word();
498        let idx_init = b.index;
499        let text_init = b.text.clone();
500        b.del().unwrap();
501        assert_eq!(b.index, idx_init);
502        assert_eq!(b.text, text_init.as_str()[1..text_init.len()]);
503    }
504
505    #[test]
506    fn test_delete_all_characters_results_blank_string() {
507        let mut b = init_with_word();
508        for _ in 0..100 {
509            b.del().unwrap();
510        }
511        assert_eq!(b.index, 0);
512        assert_eq!(b.text, "".to_string());
513    }
514
515    #[test]
516    fn test_delete_many_after_a_character_results_trimmed_string() {
517        let mut b = init_modifying_buffer();
518        let idx_init = b.index;
519        let text_init = b.text.clone();
520        for _ in 0..100 {
521            b.del().unwrap();
522        }
523        assert_eq!(b.index, idx_init);
524        assert_eq!(b.text, text_init.as_str()[0..idx_init]);
525    }
526
527    #[test]
528    fn test_go_word_foward_rearrange_cursor_to_next_word_separator() {
529        let mut b = init_modifying_buffer();
530        b.word_forward().unwrap();
531        assert_eq!(
532            b.index,
533            DUMMY_TEXT.match_indices(' ').map(|t| t.0).nth(1).unwrap()
534        );
535    }
536
537    #[test]
538    fn test_go_word_backward_rearrange_cursor_to_previous_word_head() {
539        let mut b = init_modifying_buffer();
540        b.word_backword().unwrap();
541        assert_eq!(
542            b.index,
543            DUMMY_TEXT.match_indices(' ').map(|t| t.0).nth(0).unwrap() + 1
544        );
545    }
546
547    #[test]
548    fn test_word_backspace_removes_partial_string_from_current_word() {
549        let mut b = init_modifying_buffer();
550        let idx_init = b.index;
551        let text_init = b.text.clone();
552        let idx_prev_space: usize = DUMMY_TEXT
553            .match_indices(' ')
554            .map(|t| t.0)
555            .filter(|n| *n < idx_init)
556            .last()
557            .unwrap();
558        b.word_backspace().unwrap();
559        assert_eq!(b.index, idx_prev_space);
560        assert_eq!(
561            b.text,
562            format!(
563                "{}{}",
564                &text_init[0..idx_prev_space],
565                &text_init[idx_init..text_init.len()]
566            )
567        );
568    }
569
570    #[test]
571    fn test_word_delete_removes_partial_string_from_current_word() {
572        let mut b = init_modifying_buffer();
573        let idx_init = b.index;
574        let text_init = b.text.clone();
575        let idx_next_space: usize = DUMMY_TEXT
576            .match_indices(' ')
577            .map(|t| t.0)
578            .filter(|n| *n >= idx_init)
579            .nth(0)
580            .unwrap();
581        b.word_delete().unwrap();
582        assert_eq!(b.index, idx_init);
583        assert_eq!(
584            b.text,
585            format!(
586                "{}{}",
587                &text_init[0..idx_init],
588                &text_init[idx_next_space..text_init.len()]
589            )
590        );
591    }
592
593    #[test]
594    fn test_left_key_after_characters_results_cursor_shift() {
595        let mut b = init_modifying_buffer();
596        let idx_init = b.index;
597        b.left().unwrap();
598        assert_eq!(b.index, idx_init - 1);
599    }
600
601    #[test]
602    fn test_right_key_after_all_character_results_cursor_shift() {
603        let mut b = init_with_word();
604        b.index = b.text.len();
605        let idx_init = b.text.len();
606        b.right().unwrap();
607        assert_eq!(b.index, idx_init);
608    }
609
610    #[test]
611    fn test_right_key_before_characters_results_cursor_shift() {
612        let mut b = init_with_word();
613        let idx_init = b.index;
614        b.right().unwrap();
615        assert_eq!(b.index, idx_init + 1);
616    }
617
618    #[test]
619    fn test_left_key_before_characters_has_no_effect() {
620        let mut b = init_with_word();
621        let idx_init = b.index;
622        b.left().unwrap();
623        assert_eq!(b.index, idx_init);
624    }
625
626    #[test]
627    fn test_set_prefix() {
628        let mut b = init_with_word();
629        let data = "korekara";
630        assert_eq!(b.prefix.len(), 0);
631        b.set_prefix(data.to_string());
632        assert_eq!(b.prefix.len(), data.len());
633    }
634}