repline/
repline.rs

1//! Prompts the user, reads the lines. Not much more to it than that.
2//!
3//! This module is in charge of parsing keyboard input and interpreting it for the line editor.
4
5use crate::{editor::Editor, error::*, iter::*, raw::raw};
6use std::{
7    collections::VecDeque,
8    io::{Bytes, Read, Result, Write, stdout},
9};
10
11/// Prompts the user, reads the lines. Not much more to it than that.
12#[derive(Debug)]
13pub struct Repline<'a, R: Read> {
14    input: Chars<Flatten<Result<u8>, Bytes<R>>>,
15
16    history: VecDeque<String>, // previous lines
17    hindex: usize,             // current index into the history buffer
18
19    ed: Editor<'a>, // the current line buffer
20}
21
22impl<'a> Repline<'a, std::io::Stdin> {
23    pub fn new(color: &'a str, begin: &'a str, again: &'a str) -> Self {
24        Self::with_input(std::io::stdin(), color, begin, again)
25    }
26}
27
28impl<'a, R: Read> Repline<'a, R> {
29    /// Constructs a [Repline] with the given [Reader](Read), color, begin, and again prompts.
30    pub fn with_input(input: R, color: &'a str, begin: &'a str, again: &'a str) -> Self {
31        #[allow(clippy::unbuffered_bytes)]
32        Self {
33            input: Chars(Flatten(input.bytes())),
34            history: Default::default(),
35            hindex: 0,
36            ed: Editor::new(color, begin, again),
37        }
38    }
39    /// Set the terminal prompt color
40    pub fn set_color(&mut self, color: &'a str) {
41        self.ed.color = color
42    }
43    /// Append line to history and clear it
44    pub fn accept(&mut self) {
45        self.history_append(self.ed.to_string());
46        self.ed.clear();
47        self.hindex = self.history.len();
48    }
49    /// Clear the line
50    pub fn deny(&mut self) {
51        self.ed.clear()
52    }
53    /// Reads in a line, and returns it for validation
54    pub fn read(&mut self) -> ReplResult<String> {
55        const INDENT: &str = "    ";
56        let mut stdout = stdout().lock();
57        let stdout = &mut stdout;
58        let _make_raw = raw();
59        // self.ed.begin_frame(stdout)?;
60        // self.ed.redraw_frame(stdout)?;
61        self.ed.print_head(stdout)?;
62        loop {
63            stdout.flush()?;
64            match self.input.next().ok_or(Error::EndOfInput)?? {
65                // Ctrl+C: End of Text. Immediately exits.
66                '\x03' => {
67                    drop(_make_raw);
68                    writeln!(stdout)?;
69                    return Err(Error::CtrlC(self.ed.to_string()));
70                }
71                // Ctrl+D: End of Transmission. Ends the current line.
72                '\x04' => {
73                    drop(_make_raw);
74                    writeln!(stdout)?;
75                    return Err(Error::CtrlD(self.ed.to_string()));
76                }
77                // Tab: extend line by 4 spaces
78                '\t' => {
79                    self.ed.extend(INDENT.chars(), stdout)?;
80                }
81                // ignore newlines, process line feeds. Not sure how cross-platform this is.
82                '\n' => {}
83                '\r' => {
84                    if self.ed.at_end() {
85                        self.ed.push('\n', stdout)?;
86                    } else {
87                        self.ed.end(stdout)?;
88                        writeln!(stdout)?;
89                    }
90                    return Ok(self.ed.to_string());
91                }
92                // Ctrl+Backspace in my terminal
93                '\x17' => {
94                    self.ed.erase_word(stdout)?;
95                }
96                // Escape sequence
97                '\x1b' => self.escape(stdout)?,
98                // backspace
99                '\x08' | '\x7f' => {
100                    let ed = &mut self.ed;
101                    if ed.ends_with(INDENT.chars()) {
102                        for _ in 0..INDENT.len() {
103                            ed.pop(stdout)?;
104                        }
105                    } else {
106                        ed.pop(stdout)?;
107                    }
108                }
109                c if c.is_ascii_control() => {
110                    if cfg!(debug_assertions) {
111                        self.ed.extend(c.escape_debug(), stdout)?;
112                    }
113                }
114                c => {
115                    self.ed.push(c, stdout)?;
116                }
117            }
118        }
119    }
120    /// Prints a message without moving the cursor
121    pub fn print_inline(&mut self, value: impl std::fmt::Display) -> ReplResult<()> {
122        let mut stdout = stdout().lock();
123        self.print_err(&mut stdout, value)
124    }
125    /// Prints a message (ideally an error) without moving the cursor
126    fn print_err<W: Write>(&mut self, w: &mut W, value: impl std::fmt::Display) -> ReplResult<()> {
127        self.ed.print_err(w, value)
128    }
129    /// Handle ANSI Escape
130    fn escape<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
131        match self.input.next().ok_or(Error::EndOfInput)?? {
132            '[' => self.csi(w)?,
133            'O' => todo!("Process alternate character mode"),
134            other => self.ed.extend(other.escape_debug(), w)?,
135        }
136        Ok(())
137    }
138    /// Handle ANSI Control Sequence Introducer
139    fn csi<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
140        match self.input.next().ok_or(Error::EndOfInput)?? {
141            'A' => {
142                self.hindex = self.hindex.saturating_sub(1);
143                self.restore_history(w)?
144            }
145            'B' => {
146                self.hindex = self.hindex.saturating_add(1).min(self.history.len());
147                self.restore_history(w)?
148            }
149            'C' => self.ed.cursor_forward(1, w)?,
150            'D' => self.ed.cursor_back(1, w)?,
151            'H' => self.ed.home(w)?,
152            'F' => self.ed.end(w)?,
153            '3' => {
154                if let '~' = self.input.next().ok_or(Error::EndOfInput)?? {
155                    let _ = self.ed.delete(w);
156                }
157            }
158            other => {
159                if cfg!(debug_assertions) {
160                    self.ed.extend(other.escape_debug(), w)?;
161                }
162            }
163        }
164        Ok(())
165    }
166    /// Restores the currently selected history
167    fn restore_history<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
168        let Self { history, hindex, ed, .. } = self;
169        ed.undraw(w)?;
170        ed.clear();
171        ed.print_head(w)?;
172        if let Some(history) = history.get(*hindex) {
173            ed.extend(history.chars(), w)?
174        }
175        Ok(())
176    }
177
178    /// Append line to history
179    fn history_append(&mut self, mut buf: String) {
180        while buf.ends_with(char::is_whitespace) {
181            buf.pop();
182        }
183        if !self.history.contains(&buf) {
184            self.history.push_back(buf)
185        }
186        while self.history.len() > 20 {
187            self.history.pop_front();
188        }
189    }
190}