repline/
editor.rs

1//! The [Editor] is a multi-line buffer of [`char`]s which operates on an ANSI-compatible terminal.
2
3use crossterm::{cursor::*, execute, queue, style::*, terminal::*};
4use std::{collections::VecDeque, fmt::Display, io::Write};
5
6use super::error::{Error, ReplResult};
7
8fn is_newline(c: &char) -> bool {
9    *c == '\n'
10}
11
12fn write_chars<'a, W: Write>(
13    c: impl IntoIterator<Item = &'a char>,
14    w: &mut W,
15) -> std::io::Result<()> {
16    for c in c {
17        write!(w, "{c}")?;
18    }
19    Ok(())
20}
21
22/// A multi-line editor which operates on an un-cleared ANSI terminal.
23#[derive(Clone, Debug)]
24pub struct Editor<'a> {
25    head: VecDeque<char>,
26    tail: VecDeque<char>,
27
28    pub color: &'a str,
29    begin: &'a str,
30    again: &'a str,
31}
32
33impl<'a> Editor<'a> {
34    /// Constructs a new Editor with the provided prompt color, begin prompt, and again prompt.
35    pub fn new(color: &'a str, begin: &'a str, again: &'a str) -> Self {
36        Self { head: Default::default(), tail: Default::default(), color, begin, again }
37    }
38
39    /// Returns an iterator over characters in the editor.
40    pub fn iter(&self) -> impl Iterator<Item = &char> {
41        let Self { head, tail, .. } = self;
42        head.iter().chain(tail.iter())
43    }
44
45    /// Moves up to the first line of the editor, and clears the screen.
46    ///
47    /// This assumes the screen hasn't moved since the last draw.
48    pub fn undraw<W: Write>(&self, w: &mut W) -> ReplResult<()> {
49        let Self { head, .. } = self;
50        match head.iter().copied().filter(is_newline).count() {
51            0 => write!(w, "\x1b[0G"),
52            lines => write!(w, "\x1b[{}F", lines),
53        }?;
54        queue!(w, Clear(ClearType::FromCursorDown))?;
55        // write!(w, "\x1b[0J")?;
56        Ok(())
57    }
58
59    /// Redraws the entire editor
60    pub fn redraw<W: Write>(&self, w: &mut W) -> ReplResult<()> {
61        let Self { head, tail, color, begin, again } = self;
62        write!(w, "{color}{begin}\x1b[0m ")?;
63        // draw head
64        for c in head {
65            match c {
66                '\n' => write!(w, "\r\n{color}{again}\x1b[0m "),
67                _ => w.write_all({ *c as u32 }.to_le_bytes().as_slice()),
68            }?
69        }
70        // save cursor
71        execute!(w, SavePosition)?;
72        // draw tail
73        for c in tail {
74            match c {
75                '\n' => write!(w, "\r\n{color}{again}\x1b[0m "),
76                _ => write!(w, "{c}"),
77            }?
78        }
79        // restore cursor
80        execute!(w, RestorePosition)?;
81        Ok(())
82    }
83
84    /// Prints a context-sensitive prompt (either `begin` if this is the first line,
85    /// or `again` for subsequent lines)
86    pub fn prompt<W: Write>(&self, w: &mut W) -> ReplResult<()> {
87        let Self { head, color, begin, again, .. } = self;
88        queue!(
89            w,
90            MoveToColumn(0),
91            Print(color),
92            Print(if head.is_empty() { begin } else { again }),
93            ResetColor,
94            Print(' '),
95        )?;
96        Ok(())
97    }
98
99    /// Prints the characters before the cursor on the current line.
100    pub fn print_head<W: Write>(&self, w: &mut W) -> ReplResult<()> {
101        self.prompt(w)?;
102        write_chars(
103            self.head.iter().skip(
104                self.head
105                    .iter()
106                    .rposition(is_newline)
107                    .unwrap_or(self.head.len())
108                    + 1,
109            ),
110            w,
111        )?;
112        Ok(())
113    }
114
115    pub fn print_err<W: Write>(&self, w: &mut W, err: impl Display) -> ReplResult<()> {
116        queue!(
117            w,
118            SavePosition,
119            Clear(ClearType::UntilNewLine),
120            Print(err),
121            RestorePosition
122        )?;
123        Ok(())
124    }
125
126    /// Prints the characters after the cursor on the current line.
127    pub fn print_tail<W: Write>(&self, w: &mut W) -> ReplResult<()> {
128        let Self { tail, .. } = self;
129        queue!(w, SavePosition, Clear(ClearType::UntilNewLine))?;
130        write_chars(tail.iter().take_while(|&c| !is_newline(c)), w)?;
131        queue!(w, RestorePosition)?;
132        Ok(())
133    }
134
135    /// Writes a character at the cursor, shifting the text around as necessary.
136    pub fn push<W: Write>(&mut self, c: char, w: &mut W) -> ReplResult<()> {
137        // Tail optimization: if the tail is empty,
138        //we don't have to undraw and redraw on newline
139        if self.tail.is_empty() {
140            self.head.push_back(c);
141            match c {
142                '\n' => {
143                    write!(w, "\r\n")?;
144                    self.print_head(w)?;
145                }
146                c => {
147                    queue!(w, Print(c))?;
148                }
149            };
150            return Ok(());
151        }
152
153        if '\n' == c {
154            self.undraw(w)?;
155        }
156        self.head.push_back(c);
157        match c {
158            '\n' => self.redraw(w)?,
159            _ => {
160                write!(w, "{c}")?;
161                self.print_tail(w)?;
162            }
163        }
164        Ok(())
165    }
166
167    /// Erases a character at the cursor, shifting the text around as necessary.
168    pub fn pop<W: Write>(&mut self, w: &mut W) -> ReplResult<Option<char>> {
169        if let Some('\n') = self.head.back() {
170            self.undraw(w)?;
171        }
172        let c = self.head.pop_back();
173        // if the character was a newline, we need to go back a line
174        match c {
175            Some('\n') => self.redraw(w)?,
176            Some(_) => {
177                // go back a char
178                queue!(w, MoveLeft(1), Print(' '), MoveLeft(1))?;
179                self.print_tail(w)?;
180            }
181            None => {}
182        }
183        Ok(c)
184    }
185
186    /// Writes characters into the editor at the location of the cursor.
187    pub fn extend<T: IntoIterator<Item = char>, W: Write>(
188        &mut self,
189        iter: T,
190        w: &mut W,
191    ) -> ReplResult<()> {
192        for c in iter {
193            self.push(c, w)?;
194        }
195        Ok(())
196    }
197
198    /// Sets the editor to the contents of a string, placing the cursor at the end.
199    pub fn restore(&mut self, s: &str) {
200        self.clear();
201        self.head.extend(s.chars())
202    }
203
204    /// Clears the editor, removing all characters.
205    pub fn clear(&mut self) {
206        self.head.clear();
207        self.tail.clear();
208    }
209
210    /// Pops the character after the cursor, redrawing if necessary
211    pub fn delete<W: Write>(&mut self, w: &mut W) -> ReplResult<char> {
212        match self.tail.front() {
213            Some('\n') => {
214                self.undraw(w)?;
215                let out = self.tail.pop_front();
216                self.redraw(w)?;
217                out
218            }
219            _ => {
220                let out = self.tail.pop_front();
221                self.print_tail(w)?;
222                out
223            }
224        }
225        .ok_or(Error::EndOfInput)
226    }
227
228    /// Erases a word from the buffer, where a word is any non-whitespace characters
229    /// preceded by a single whitespace character
230    pub fn erase_word<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
231        while self.pop(w)?.filter(|c| !c.is_whitespace()).is_some() {}
232        Ok(())
233    }
234
235    /// Returns the number of characters in the buffer
236    pub fn len(&self) -> usize {
237        self.head.len() + self.tail.len()
238    }
239
240    /// Returns true if the cursor is at the beginning
241    pub fn at_start(&self) -> bool {
242        self.head.is_empty()
243    }
244    /// Returns true if the cursor is at the end
245    pub fn at_end(&self) -> bool {
246        self.tail.is_empty()
247    }
248
249    /// Returns true if the buffer is empty.
250    pub fn is_empty(&self) -> bool {
251        self.at_start() && self.at_end()
252    }
253
254    /// Returns true if the buffer ends with a given pattern
255    pub fn ends_with(&self, iter: impl DoubleEndedIterator<Item = char>) -> bool {
256        let mut iter = iter.rev();
257        let mut head = self.head.iter().rev();
258        loop {
259            match (iter.next(), head.next()) {
260                (None, _) => break true,
261                (Some(_), None) => break false,
262                (Some(a), Some(b)) if a != *b => break false,
263                (Some(_), Some(_)) => continue,
264            }
265        }
266    }
267
268    /// Moves the cursor back `steps` steps
269    pub fn cursor_back<W: Write>(&mut self, steps: usize, w: &mut W) -> ReplResult<()> {
270        for _ in 0..steps {
271            if let Some('\n') = self.head.back() {
272                self.undraw(w)?;
273            }
274            let Some(c) = self.head.pop_back() else {
275                return Ok(());
276            };
277            self.tail.push_front(c);
278            match c {
279                '\n' => self.redraw(w)?,
280                _ => queue!(w, MoveLeft(1))?,
281            }
282        }
283        Ok(())
284    }
285
286    /// Moves the cursor forward `steps` steps
287    pub fn cursor_forward<W: Write>(&mut self, steps: usize, w: &mut W) -> ReplResult<()> {
288        for _ in 0..steps {
289            if let Some('\n') = self.tail.front() {
290                self.undraw(w)?
291            }
292            let Some(c) = self.tail.pop_front() else {
293                return Ok(());
294            };
295            self.head.push_back(c);
296            match c {
297                '\n' => self.redraw(w)?,
298                _ => queue!(w, MoveRight(1))?,
299            }
300        }
301        Ok(())
302    }
303
304    /// Moves the cursor to the beginning of the current line
305    pub fn home<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
306        loop {
307            match self.head.back() {
308                Some('\n') | None => break Ok(()),
309                Some(_) => self.cursor_back(1, w)?,
310            }
311        }
312    }
313
314    /// Moves the cursor to the end of the current line
315    pub fn end<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
316        loop {
317            match self.tail.front() {
318                Some('\n') | None => break Ok(()),
319                Some(_) => self.cursor_forward(1, w)?,
320            }
321        }
322    }
323}
324
325impl<'e> IntoIterator for &'e Editor<'_> {
326    type Item = &'e char;
327    type IntoIter = std::iter::Chain<
328        std::collections::vec_deque::Iter<'e, char>,
329        std::collections::vec_deque::Iter<'e, char>,
330    >;
331    fn into_iter(self) -> Self::IntoIter {
332        self.head.iter().chain(self.tail.iter())
333    }
334}
335
336impl Display for Editor<'_> {
337    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338        use std::fmt::Write;
339        for c in self.iter() {
340            f.write_char(*c)?;
341        }
342        Ok(())
343    }
344}