1 //! This module implements and describes common TTY methods & traits
2 
3 use unicode_width::UnicodeWidthStr;
4 
5 use crate::config::{BellStyle, ColorMode, Config, OutputStreamType};
6 use crate::highlight::Highlighter;
7 use crate::keys::KeyEvent;
8 use crate::layout::{Layout, Position};
9 use crate::line_buffer::LineBuffer;
10 use crate::Result;
11 
12 /// Terminal state
13 pub trait RawMode: Sized {
14     /// Disable RAW mode for the terminal.
disable_raw_mode(&self) -> Result<()>15     fn disable_raw_mode(&self) -> Result<()>;
16 }
17 
18 /// Translate bytes read from stdin to keys.
19 pub trait RawReader {
20     /// Blocking read of key pressed.
next_key(&mut self, single_esc_abort: bool) -> Result<KeyEvent>21     fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyEvent>;
22     /// For CTRL-V support
23     #[cfg(unix)]
next_char(&mut self) -> Result<char>24     fn next_char(&mut self) -> Result<char>;
25     /// Bracketed paste
read_pasted_text(&mut self) -> Result<String>26     fn read_pasted_text(&mut self) -> Result<String>;
27 }
28 
29 /// Display prompt, line and cursor in terminal output
30 pub trait Renderer {
31     type Reader: RawReader;
32 
move_cursor(&mut self, old: Position, new: Position) -> Result<()>33     fn move_cursor(&mut self, old: Position, new: Position) -> Result<()>;
34 
35     /// Display `prompt`, line and cursor in terminal output
36     #[allow(clippy::too_many_arguments)]
refresh_line( &mut self, prompt: &str, line: &LineBuffer, hint: Option<&str>, old_layout: &Layout, new_layout: &Layout, highlighter: Option<&dyn Highlighter>, ) -> Result<()>37     fn refresh_line(
38         &mut self,
39         prompt: &str,
40         line: &LineBuffer,
41         hint: Option<&str>,
42         old_layout: &Layout,
43         new_layout: &Layout,
44         highlighter: Option<&dyn Highlighter>,
45     ) -> Result<()>;
46 
47     /// Compute layout for rendering prompt + line + some info (either hint,
48     /// validation msg, ...). on the screen. Depending on screen width, line
49     /// wrapping may be applied.
compute_layout( &self, prompt_size: Position, default_prompt: bool, line: &LineBuffer, info: Option<&str>, ) -> Layout50     fn compute_layout(
51         &self,
52         prompt_size: Position,
53         default_prompt: bool,
54         line: &LineBuffer,
55         info: Option<&str>,
56     ) -> Layout {
57         // calculate the desired position of the cursor
58         let pos = line.pos();
59         let cursor = self.calculate_position(&line[..pos], prompt_size);
60         // calculate the position of the end of the input line
61         let mut end = if pos == line.len() {
62             cursor
63         } else {
64             self.calculate_position(&line[pos..], cursor)
65         };
66         if let Some(info) = info {
67             end = self.calculate_position(info, end);
68         }
69 
70         let new_layout = Layout {
71             prompt_size,
72             default_prompt,
73             cursor,
74             end,
75         };
76         debug_assert!(new_layout.prompt_size <= new_layout.cursor);
77         debug_assert!(new_layout.cursor <= new_layout.end);
78         new_layout
79     }
80 
81     /// Calculate the number of columns and rows used to display `s` on a
82     /// `cols` width terminal starting at `orig`.
calculate_position(&self, s: &str, orig: Position) -> Position83     fn calculate_position(&self, s: &str, orig: Position) -> Position;
84 
write_and_flush(&self, buf: &[u8]) -> Result<()>85     fn write_and_flush(&self, buf: &[u8]) -> Result<()>;
86 
87     /// Beep, used for completion when there is nothing to complete or when all
88     /// the choices were already shown.
beep(&mut self) -> Result<()>89     fn beep(&mut self) -> Result<()>;
90 
91     /// Clear the screen. Used to handle ctrl+l
clear_screen(&mut self) -> Result<()>92     fn clear_screen(&mut self) -> Result<()>;
93 
94     /// Check if a SIGWINCH signal has been received
sigwinch(&self) -> bool95     fn sigwinch(&self) -> bool;
96     /// Update the number of columns/rows in the current terminal.
update_size(&mut self)97     fn update_size(&mut self);
98     /// Get the number of columns in the current terminal.
get_columns(&self) -> usize99     fn get_columns(&self) -> usize;
100     /// Get the number of rows in the current terminal.
get_rows(&self) -> usize101     fn get_rows(&self) -> usize;
102     /// Check if output supports colors.
colors_enabled(&self) -> bool103     fn colors_enabled(&self) -> bool;
104 
105     /// Make sure prompt is at the leftmost edge of the screen
move_cursor_at_leftmost(&mut self, rdr: &mut Self::Reader) -> Result<()>106     fn move_cursor_at_leftmost(&mut self, rdr: &mut Self::Reader) -> Result<()>;
107 }
108 
109 impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R {
110     type Reader = R::Reader;
111 
move_cursor(&mut self, old: Position, new: Position) -> Result<()>112     fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> {
113         (**self).move_cursor(old, new)
114     }
115 
refresh_line( &mut self, prompt: &str, line: &LineBuffer, hint: Option<&str>, old_layout: &Layout, new_layout: &Layout, highlighter: Option<&dyn Highlighter>, ) -> Result<()>116     fn refresh_line(
117         &mut self,
118         prompt: &str,
119         line: &LineBuffer,
120         hint: Option<&str>,
121         old_layout: &Layout,
122         new_layout: &Layout,
123         highlighter: Option<&dyn Highlighter>,
124     ) -> Result<()> {
125         (**self).refresh_line(prompt, line, hint, old_layout, new_layout, highlighter)
126     }
127 
calculate_position(&self, s: &str, orig: Position) -> Position128     fn calculate_position(&self, s: &str, orig: Position) -> Position {
129         (**self).calculate_position(s, orig)
130     }
131 
write_and_flush(&self, buf: &[u8]) -> Result<()>132     fn write_and_flush(&self, buf: &[u8]) -> Result<()> {
133         (**self).write_and_flush(buf)
134     }
135 
beep(&mut self) -> Result<()>136     fn beep(&mut self) -> Result<()> {
137         (**self).beep()
138     }
139 
clear_screen(&mut self) -> Result<()>140     fn clear_screen(&mut self) -> Result<()> {
141         (**self).clear_screen()
142     }
143 
sigwinch(&self) -> bool144     fn sigwinch(&self) -> bool {
145         (**self).sigwinch()
146     }
147 
update_size(&mut self)148     fn update_size(&mut self) {
149         (**self).update_size()
150     }
151 
get_columns(&self) -> usize152     fn get_columns(&self) -> usize {
153         (**self).get_columns()
154     }
155 
get_rows(&self) -> usize156     fn get_rows(&self) -> usize {
157         (**self).get_rows()
158     }
159 
colors_enabled(&self) -> bool160     fn colors_enabled(&self) -> bool {
161         (**self).colors_enabled()
162     }
163 
move_cursor_at_leftmost(&mut self, rdr: &mut R::Reader) -> Result<()>164     fn move_cursor_at_leftmost(&mut self, rdr: &mut R::Reader) -> Result<()> {
165         (**self).move_cursor_at_leftmost(rdr)
166     }
167 }
168 
169 // ignore ANSI escape sequence
width(s: &str, esc_seq: &mut u8) -> usize170 fn width(s: &str, esc_seq: &mut u8) -> usize {
171     if *esc_seq == 1 {
172         if s == "[" {
173             // CSI
174             *esc_seq = 2;
175         } else {
176             // two-character sequence
177             *esc_seq = 0;
178         }
179         0
180     } else if *esc_seq == 2 {
181         if s == ";" || (s.as_bytes()[0] >= b'0' && s.as_bytes()[0] <= b'9') {
182             /*} else if s == "m" {
183             // last
184              *esc_seq = 0;*/
185         } else {
186             // not supported
187             *esc_seq = 0;
188         }
189         0
190     } else if s == "\x1b" {
191         *esc_seq = 1;
192         0
193     } else if s == "\n" {
194         0
195     } else {
196         s.width()
197     }
198 }
199 
200 /// Terminal contract
201 pub trait Term {
202     type Reader: RawReader; // rl_instream
203     type Writer: Renderer<Reader = Self::Reader>; // rl_outstream
204     type Mode: RawMode;
205 
new( color_mode: ColorMode, stream: OutputStreamType, tab_stop: usize, bell_style: BellStyle, enable_bracketed_paste: bool, ) -> Self206     fn new(
207         color_mode: ColorMode,
208         stream: OutputStreamType,
209         tab_stop: usize,
210         bell_style: BellStyle,
211         enable_bracketed_paste: bool,
212     ) -> Self;
213     /// Check if current terminal can provide a rich line-editing user
214     /// interface.
is_unsupported(&self) -> bool215     fn is_unsupported(&self) -> bool;
216     /// check if stdin is connected to a terminal.
is_stdin_tty(&self) -> bool217     fn is_stdin_tty(&self) -> bool;
218     /// check if output stream is connected to a terminal.
is_output_tty(&self) -> bool219     fn is_output_tty(&self) -> bool;
220     /// Enable RAW mode for the terminal.
enable_raw_mode(&mut self) -> Result<Self::Mode>221     fn enable_raw_mode(&mut self) -> Result<Self::Mode>;
222     /// Create a RAW reader
create_reader(&self, config: &Config) -> Result<Self::Reader>223     fn create_reader(&self, config: &Config) -> Result<Self::Reader>;
224     /// Create a writer
create_writer(&self) -> Self::Writer225     fn create_writer(&self) -> Self::Writer;
226 }
227 
228 // If on Windows platform import Windows TTY module
229 // and re-export into mod.rs scope
230 #[cfg(all(windows, not(target_arch = "wasm32")))]
231 mod windows;
232 #[cfg(all(windows, not(target_arch = "wasm32")))]
233 pub use self::windows::*;
234 
235 // If on Unix platform import Unix TTY module
236 // and re-export into mod.rs scope
237 #[cfg(all(unix, not(target_arch = "wasm32")))]
238 mod unix;
239 #[cfg(all(unix, not(target_arch = "wasm32")))]
240 pub use self::unix::*;
241 
242 #[cfg(any(test, target_arch = "wasm32"))]
243 mod test;
244 #[cfg(any(test, target_arch = "wasm32"))]
245 pub use self::test::*;
246