Skip to main content

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::*, queue, style::*, terminal::*};
4use std::{collections::VecDeque, fmt::Display, io::Write};
5
6use super::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        queue!(w, Print(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    pub begin: &'a str,
30    pub 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    fn putchar<W: Write>(&self, c: char, w: &mut W) -> ReplResult<()> {
46        let Self { color, again, .. } = self;
47        match c {
48            '\n' => queue!(
49                w,
50                Print('\n'),
51                MoveToColumn(0),
52                Print(color),
53                Print(again),
54                Print(ResetColor)
55            ),
56            c => queue!(w, Print(c)),
57        }?;
58        Ok(())
59    }
60
61    pub fn redraw_head<W: Write>(&self, w: &mut W) -> ReplResult<()> {
62        let Self { head, color, begin, .. } = self;
63        match head.iter().copied().filter(is_newline).count() {
64            0 => queue!(w, MoveToColumn(0)),
65            n => queue!(w, MoveUp(n as u16)),
66        }?;
67
68        queue!(w, Print(color), Print(begin), Print(ResetColor))?;
69        for c in head {
70            self.putchar(*c, w)?;
71        }
72        Ok(())
73    }
74
75    pub fn redraw_tail<W: Write>(&self, w: &mut W) -> ReplResult<()> {
76        let Self { tail, .. } = self;
77        queue!(w, SavePosition, Clear(ClearType::FromCursorDown))?;
78        for c in tail {
79            self.putchar(*c, w)?;
80        }
81        queue!(w, RestorePosition)?;
82        Ok(())
83    }
84
85    /// Prints the characters before the cursor on the current line.
86    pub fn print_head<W: Write>(&self, w: &mut W) -> ReplResult<()> {
87        let Self { head, color, begin, again, .. } = self;
88        let nl = self.head.iter().rposition(is_newline).map(|n| n + 1);
89        let prompt = if nl.is_some() { again } else { begin };
90
91        queue!(w, MoveToColumn(0), Print(color), Print(prompt), ResetColor)?;
92
93        write_chars(head.iter().skip(nl.unwrap_or(0)), w)?;
94        Ok(())
95    }
96
97    /// Prints the characters after the cursor on the current line.
98    pub fn print_tail<W: Write>(&self, w: &mut W) -> ReplResult<()> {
99        let Self { tail, .. } = self;
100        queue!(w, SavePosition, Clear(ClearType::UntilNewLine))?;
101        write_chars(tail.iter().take_while(|&c| !is_newline(c)), w)?;
102        queue!(w, RestorePosition)?;
103        Ok(())
104    }
105
106    pub fn print_err<W: Write>(&self, err: impl Display, w: &mut W) -> ReplResult<()> {
107        queue!(
108            w,
109            SavePosition,
110            Clear(ClearType::UntilNewLine),
111            Print(err),
112            RestorePosition
113        )?;
114        Ok(())
115    }
116
117    /// Writes a character at the cursor, shifting the text around as necessary.
118    pub fn push<W: Write>(&mut self, c: char, w: &mut W) -> ReplResult<()> {
119        self.head.push_back(c);
120        queue!(w, Clear(ClearType::UntilNewLine))?;
121        self.putchar(c, w)?;
122        match c {
123            '\n' => self.redraw_tail(w),
124            _ => self.print_tail(w),
125        }
126    }
127
128    /// Erases a character at the cursor, shifting the text around as necessary.
129    pub fn pop<W: Write>(&mut self, w: &mut W) -> ReplResult<Option<char>> {
130        let c = self.head.pop_back();
131
132        match c {
133            None => return Ok(None),
134            Some('\n') => {
135                queue!(w, MoveToPreviousLine(1))?;
136                self.print_head(w)?;
137                self.redraw_tail(w)?;
138            }
139            Some(_) => {
140                queue!(w, MoveLeft(1), Clear(ClearType::UntilNewLine))?;
141                self.print_tail(w)?;
142            }
143        }
144
145        Ok(c)
146    }
147
148    /// Pops the character after the cursor, redrawing if necessary
149    pub fn delete<W: Write>(&mut self, w: &mut W) -> ReplResult<Option<char>> {
150        let c = self.tail.pop_front();
151        match c {
152            Some('\n') => self.redraw_tail(w)?,
153            _ => self.print_tail(w)?,
154        }
155        Ok(c)
156    }
157
158    /// Writes characters into the editor at the location of the cursor.
159    pub fn extend<T: IntoIterator<Item = char>, W: Write>(
160        &mut self,
161        iter: T,
162        w: &mut W,
163    ) -> ReplResult<()> {
164        for c in iter {
165            self.push(c, w)?;
166        }
167        Ok(())
168    }
169
170    /// Sets the editor to the contents of a string, placing the cursor at the end.
171    pub fn restore<W: Write>(&mut self, s: &str, w: &mut W) -> ReplResult<()> {
172        match self.head.iter().copied().filter(is_newline).count() {
173            0 => queue!(w, MoveToColumn(0), Clear(ClearType::FromCursorDown))?,
174            n => queue!(w, MoveUp(n as u16), Clear(ClearType::FromCursorDown))?,
175        };
176        self.clear();
177        self.print_head(w)?;
178        self.extend(s.chars(), w)
179    }
180
181    /// Clears the editor, removing all characters.
182    pub fn clear(&mut self) {
183        self.head.clear();
184        self.tail.clear();
185    }
186
187    /// Erases a word from the buffer, where a word is any non-whitespace characters
188    /// preceded by a single whitespace character
189    pub fn erase_word<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
190        while self.pop(w)?.filter(|c| !c.is_whitespace()).is_some() {}
191        Ok(())
192    }
193
194    /// Returns the number of characters in the buffer
195    pub fn len(&self) -> usize {
196        self.head.len() + self.tail.len()
197    }
198
199    /// Returns true if the cursor is at the start of the buffer
200    pub fn at_start(&self) -> bool {
201        self.head.is_empty()
202    }
203
204    /// Returns true if the cursor is at the end of the buffer
205    pub fn at_end(&self) -> bool {
206        self.tail.is_empty()
207    }
208
209    /// Returns true if the cursor is at the start of a line
210    pub fn at_line_start(&self) -> bool {
211        matches!(self.head.back(), None | Some('\n'))
212    }
213
214    /// Returns true if the cursor is at the end of a line
215    pub fn at_line_end(&self) -> bool {
216        matches!(self.tail.front(), None | Some('\n'))
217    }
218
219    /// Returns true if the character before the cursor is whitespace
220    pub fn at_word_start(&self) -> bool {
221        self.head
222            .back()
223            .copied()
224            .map(|c| c.is_alphanumeric() || c == '\n')
225            .unwrap_or(true)
226    }
227
228    /// Returns true if the character after the cursor is whitespace
229    pub fn at_word_end(&self) -> bool {
230        self.tail
231            .front()
232            .copied()
233            .map(|c| c.is_alphanumeric() || c == '\n')
234            .unwrap_or(true)
235    }
236
237    /// Returns true if the buffer is empty.
238    pub fn is_empty(&self) -> bool {
239        self.at_start() && self.at_end()
240    }
241
242    /// Returns true if the buffer ends with a given pattern
243    pub fn ends_with(&self, iter: impl DoubleEndedIterator<Item = char>) -> bool {
244        let mut iter = iter.rev();
245        let mut head = self.head.iter().rev();
246        loop {
247            match (iter.next(), head.next()) {
248                (None, _) => break true,
249                (Some(_), None) => break false,
250                (Some(a), Some(b)) if a != *b => break false,
251                (Some(_), Some(_)) => continue,
252            }
253        }
254    }
255
256    /// Moves the cursor back `steps` steps
257    pub fn cursor_back<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
258        let Some(c) = self.head.pop_back() else {
259            return Ok(());
260        };
261
262        self.tail.push_front(c);
263        match c {
264            '\n' => {
265                queue!(w, MoveToPreviousLine(1))?;
266                self.print_head(w)
267            }
268            _ => queue!(w, MoveLeft(1)).map_err(Into::into),
269        }
270    }
271
272    /// Moves the cursor forward `steps` steps
273    pub fn cursor_forward<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
274        let Some(c) = self.tail.pop_front() else {
275            return Ok(());
276        };
277
278        self.head.push_back(c);
279        match c {
280            '\n' => {
281                queue!(w, MoveToNextLine(1))?;
282                self.print_head(w)
283            }
284            _ => queue!(w, MoveRight(1)).map_err(Into::into),
285        }
286    }
287
288    /// Moves the cursor up to the previous line, attempting to preserve relative offset
289    pub fn cursor_up<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
290        // Calculates length of the current line
291        let mut len = self.head.len();
292        self.cursor_line_start(w)?;
293        len -= self.head.len();
294
295        if self.at_start() {
296            return Ok(());
297        }
298
299        self.cursor_back(w)?;
300        self.cursor_line_start(w)?;
301
302        while 0 < len && !self.at_line_end() {
303            self.cursor_forward(w)?;
304            len -= 1;
305        }
306
307        Ok(())
308    }
309
310    /// Moves the cursor down to the next line, attempting to preserve relative offset
311    pub fn cursor_down<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
312        let mut len = self.head.iter().rev().take_while(|&&c| c != '\n').count();
313
314        self.cursor_line_end(w)?;
315        self.cursor_forward(w)?;
316
317        while 0 < len && !self.at_line_end() {
318            self.cursor_forward(w)?;
319            len -= 1;
320        }
321
322        Ok(())
323    }
324
325    /// Moves the cursor to the beginning of the current line
326    pub fn cursor_line_start<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
327        while !self.at_line_start() {
328            self.cursor_back(w)?
329        }
330        Ok(())
331    }
332
333    /// Moves the cursor to the end of the current line
334    pub fn cursor_line_end<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
335        while !self.at_line_end() {
336            self.cursor_forward(w)?
337        }
338        Ok(())
339    }
340
341    /// Moves the cursor to the previous whitespace boundary
342    pub fn cursor_word_back<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
343        let target = self.at_word_start();
344        self.cursor_back(w)?;
345        while self.at_word_start() == target && !self.at_start() {
346            self.cursor_back(w)?
347        }
348        Ok(())
349    }
350
351    /// Moves the cursor to the next whitespace boundary
352    pub fn cursor_word_forward<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
353        let target = self.at_word_end();
354        self.cursor_forward(w)?;
355        while self.at_word_end() == target && !self.at_end() {
356            self.cursor_forward(w)?
357        }
358        Ok(())
359    }
360
361    /// Moves the cursor to the start of the buffer
362    pub fn cursor_start<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
363        while !self.at_start() {
364            self.cursor_back(w)?
365        }
366        Ok(())
367    }
368
369    /// Moves the cursor to the end of the buffer
370    pub fn cursor_end<W: Write>(&mut self, w: &mut W) -> ReplResult<()> {
371        while !self.at_end() {
372            self.cursor_forward(w)?
373        }
374        Ok(())
375    }
376}
377
378impl<'e> IntoIterator for &'e Editor<'_> {
379    type Item = &'e char;
380    type IntoIter = std::iter::Chain<
381        std::collections::vec_deque::Iter<'e, char>,
382        std::collections::vec_deque::Iter<'e, char>,
383    >;
384    fn into_iter(self) -> Self::IntoIter {
385        self.head.iter().chain(self.tail.iter())
386    }
387}
388
389impl Display for Editor<'_> {
390    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391        use std::fmt::Write;
392        for c in self.iter() {
393            f.write_char(*c)?;
394        }
395        Ok(())
396    }
397}