rustyline/tty/
mod.rs

1//! This module implements and describes common TTY methods & traits
2
3use unicode_width::UnicodeWidthStr;
4
5use crate::config::{Behavior, BellStyle, ColorMode, Config};
6use crate::highlight::Highlighter;
7use crate::keys::KeyEvent;
8use crate::layout::{Layout, Position};
9use crate::line_buffer::LineBuffer;
10use crate::{Cmd, Result};
11
12/// Terminal state
13pub trait RawMode: Sized {
14    /// Disable RAW mode for the terminal.
15    fn disable_raw_mode(&self) -> Result<()>;
16}
17
18/// Input event
19pub enum Event {
20    KeyPress(KeyEvent),
21    ExternalPrint(String),
22}
23
24/// Translate bytes read from stdin to keys.
25pub trait RawReader {
26    type Buffer;
27    /// Blocking wait for either a key press or an external print
28    fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event>; // TODO replace calls to `next_key` by `wait_for_input` where relevant
29    /// Blocking read of key pressed.
30    fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyEvent>;
31    /// For CTRL-V support
32    #[cfg(unix)]
33    fn next_char(&mut self) -> Result<char>;
34    /// Bracketed paste
35    fn read_pasted_text(&mut self) -> Result<String>;
36    /// Check if `key` is bound to a peculiar command
37    fn find_binding(&self, key: &KeyEvent) -> Option<Cmd>;
38    /// Backup type ahead
39    fn unbuffer(self) -> Option<Buffer>;
40}
41
42/// Display prompt, line and cursor in terminal output
43pub trait Renderer {
44    type Reader: RawReader;
45
46    fn move_cursor(&mut self, old: Position, new: Position) -> Result<()>;
47
48    /// Display `prompt`, line and cursor in terminal output
49    #[allow(clippy::too_many_arguments)]
50    fn refresh_line(
51        &mut self,
52        prompt: &str,
53        line: &LineBuffer,
54        hint: Option<&str>,
55        old_layout: &Layout,
56        new_layout: &Layout,
57        highlighter: Option<&dyn Highlighter>,
58    ) -> Result<()>;
59
60    /// Compute layout for rendering prompt + line + some info (either hint,
61    /// validation msg, ...). on the screen. Depending on screen width, line
62    /// wrapping may be applied.
63    fn compute_layout(
64        &self,
65        prompt_size: Position,
66        default_prompt: bool,
67        line: &LineBuffer,
68        info: Option<&str>,
69    ) -> Layout {
70        // calculate the desired position of the cursor
71        let pos = line.pos();
72        let cursor = self.calculate_position(&line[..pos], prompt_size);
73        // calculate the position of the end of the input line
74        let mut end = if pos == line.len() {
75            cursor
76        } else {
77            self.calculate_position(&line[pos..], cursor)
78        };
79        if let Some(info) = info {
80            end = self.calculate_position(info, end);
81        }
82
83        let new_layout = Layout {
84            prompt_size,
85            default_prompt,
86            cursor,
87            end,
88        };
89        debug_assert!(new_layout.prompt_size <= new_layout.cursor);
90        debug_assert!(new_layout.cursor <= new_layout.end);
91        new_layout
92    }
93
94    /// Calculate the number of columns and rows used to display `s` on a
95    /// `cols` width terminal starting at `orig`.
96    fn calculate_position(&self, s: &str, orig: Position) -> Position;
97
98    fn write_and_flush(&mut self, buf: &str) -> Result<()>;
99
100    /// Beep, used for completion when there is nothing to complete or when all
101    /// the choices were already shown.
102    fn beep(&mut self) -> Result<()>;
103
104    /// Clear the screen. Used to handle ctrl+l
105    fn clear_screen(&mut self) -> Result<()>;
106    /// Clear rows used by prompt and edited line
107    fn clear_rows(&mut self, layout: &Layout) -> Result<()>;
108
109    /// Update the number of columns/rows in the current terminal.
110    fn update_size(&mut self);
111    /// Get the number of columns in the current terminal.
112    fn get_columns(&self) -> usize;
113    /// Get the number of rows in the current terminal.
114    fn get_rows(&self) -> usize;
115    /// Check if output supports colors.
116    fn colors_enabled(&self) -> bool;
117
118    /// Make sure prompt is at the leftmost edge of the screen
119    fn move_cursor_at_leftmost(&mut self, rdr: &mut Self::Reader) -> Result<()>;
120}
121
122impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R {
123    type Reader = R::Reader;
124
125    fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> {
126        (**self).move_cursor(old, new)
127    }
128
129    fn refresh_line(
130        &mut self,
131        prompt: &str,
132        line: &LineBuffer,
133        hint: Option<&str>,
134        old_layout: &Layout,
135        new_layout: &Layout,
136        highlighter: Option<&dyn Highlighter>,
137    ) -> Result<()> {
138        (**self).refresh_line(prompt, line, hint, old_layout, new_layout, highlighter)
139    }
140
141    fn calculate_position(&self, s: &str, orig: Position) -> Position {
142        (**self).calculate_position(s, orig)
143    }
144
145    fn write_and_flush(&mut self, buf: &str) -> Result<()> {
146        (**self).write_and_flush(buf)
147    }
148
149    fn beep(&mut self) -> Result<()> {
150        (**self).beep()
151    }
152
153    fn clear_screen(&mut self) -> Result<()> {
154        (**self).clear_screen()
155    }
156
157    fn clear_rows(&mut self, layout: &Layout) -> Result<()> {
158        (**self).clear_rows(layout)
159    }
160
161    fn update_size(&mut self) {
162        (**self).update_size();
163    }
164
165    fn get_columns(&self) -> usize {
166        (**self).get_columns()
167    }
168
169    fn get_rows(&self) -> usize {
170        (**self).get_rows()
171    }
172
173    fn colors_enabled(&self) -> bool {
174        (**self).colors_enabled()
175    }
176
177    fn move_cursor_at_leftmost(&mut self, rdr: &mut R::Reader) -> Result<()> {
178        (**self).move_cursor_at_leftmost(rdr)
179    }
180}
181
182// ignore ANSI escape sequence
183fn width(s: &str, esc_seq: &mut u8) -> usize {
184    if *esc_seq == 1 {
185        if s == "[" {
186            // CSI
187            *esc_seq = 2;
188        } else {
189            // two-character sequence
190            *esc_seq = 0;
191        }
192        0
193    } else if *esc_seq == 2 {
194        if s == ";" || (s.as_bytes()[0] >= b'0' && s.as_bytes()[0] <= b'9') {
195            /*} else if s == "m" {
196            // last
197             *esc_seq = 0;*/
198        } else {
199            // not supported
200            *esc_seq = 0;
201        }
202        0
203    } else if s == "\x1b" {
204        *esc_seq = 1;
205        0
206    } else if s == "\n" {
207        0
208    } else {
209        s.width()
210    }
211}
212
213/// External printer
214pub trait ExternalPrinter {
215    /// Print message to stdout
216    fn print(&mut self, msg: String) -> Result<()>;
217}
218
219/// Terminal contract
220pub trait Term {
221    type Buffer;
222    type KeyMap;
223    type Reader: RawReader<Buffer = Self::Buffer>; // rl_instream
224    type Writer: Renderer<Reader = Self::Reader>; // rl_outstream
225    type Mode: RawMode;
226    type ExternalPrinter: ExternalPrinter;
227    type CursorGuard;
228
229    fn new(
230        color_mode: ColorMode,
231        behavior: Behavior,
232        tab_stop: usize,
233        bell_style: BellStyle,
234        enable_bracketed_paste: bool,
235        enable_signals: bool,
236    ) -> Result<Self>
237    where
238        Self: Sized;
239    /// Check if current terminal can provide a rich line-editing user
240    /// interface.
241    fn is_unsupported(&self) -> bool;
242    /// check if input stream is connected to a terminal.
243    fn is_input_tty(&self) -> bool;
244    /// check if output stream is connected to a terminal.
245    fn is_output_tty(&self) -> bool;
246    /// Enable RAW mode for the terminal.
247    fn enable_raw_mode(&mut self) -> Result<(Self::Mode, Self::KeyMap)>;
248    /// Create a RAW reader
249    fn create_reader(
250        &self,
251        buffer: Option<Self::Buffer>,
252        config: &Config,
253        key_map: Self::KeyMap,
254    ) -> Self::Reader;
255    /// Create a writer
256    fn create_writer(&self) -> Self::Writer;
257    fn writeln(&self) -> Result<()>;
258    /// Create an external printer
259    fn create_external_printer(&mut self) -> Result<Self::ExternalPrinter>;
260    /// Change cursor visibility
261    fn set_cursor_visibility(&mut self, visible: bool) -> Result<Option<Self::CursorGuard>>;
262}
263
264// If on Windows platform import Windows TTY module
265// and re-export into mod.rs scope
266#[cfg(all(windows, not(target_arch = "wasm32")))]
267mod windows;
268#[cfg(all(windows, not(target_arch = "wasm32"), not(test)))]
269pub use self::windows::*;
270
271// If on Unix platform import Unix TTY module
272// and re-export into mod.rs scope
273#[cfg(all(unix, not(target_arch = "wasm32")))]
274mod unix;
275#[cfg(all(unix, not(target_arch = "wasm32"), not(test)))]
276pub use self::unix::*;
277
278#[cfg(any(test, target_arch = "wasm32"))]
279mod test;
280#[cfg(any(test, target_arch = "wasm32"))]
281pub use self::test::*;