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