rustyline/tty/
mod.rs

1//! This module implements and describes common TTY methods & traits
2use std::io::{self, Write};
3use unicode_segmentation::UnicodeSegmentation;
4use unicode_width::UnicodeWidthStr;
5
6use config::{ColorMode, Config, OutputStreamType};
7use highlight::Highlighter;
8use keys::KeyPress;
9use line_buffer::LineBuffer;
10use 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/// Translate bytes read from stdin to keys.
19pub trait RawReader {
20    /// Blocking read of key pressed.
21    fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyPress>;
22    /// For CTRL-V support
23    #[cfg(unix)]
24    fn next_char(&mut self) -> Result<char>;
25}
26
27#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
28pub struct Position {
29    pub col: usize,
30    pub row: usize,
31}
32
33/// Display prompt, line and cursor in terminal output
34pub trait Renderer {
35    fn move_cursor(&mut self, old: Position, new: Position) -> Result<()>;
36
37    /// Display `prompt`, line and cursor in terminal output
38    fn refresh_line(
39        &mut self,
40        prompt: &str,
41        prompt_size: Position,
42        line: &LineBuffer,
43        hint: Option<String>,
44        current_row: usize,
45        old_rows: usize,
46        highlighter: Option<&dyn Highlighter>,
47    ) -> Result<(Position, Position)>;
48
49    /// Calculate the number of columns and rows used to display `s` on a
50    /// `cols` width terminal starting at `orig`.
51    fn calculate_position(&self, s: &str, orig: Position) -> Position;
52
53    fn write_and_flush(&mut self, buf: &[u8]) -> Result<()>;
54
55    /// Beep, used for completion when there is nothing to complete or when all
56    /// the choices were already shown.
57    fn beep(&mut self) -> Result<()> {
58        // TODO bell-style
59        try!(io::stderr().write_all(b"\x07"));
60        try!(io::stderr().flush());
61        Ok(())
62    }
63
64    /// Clear the screen. Used to handle ctrl+l
65    fn clear_screen(&mut self) -> Result<()>;
66
67    /// Check if a SIGWINCH signal has been received
68    fn sigwinch(&self) -> bool;
69    /// Update the number of columns/rows in the current terminal.
70    fn update_size(&mut self);
71    /// Get the number of columns in the current terminal.
72    fn get_columns(&self) -> usize;
73    /// Get the number of rows in the current terminal.
74    fn get_rows(&self) -> usize;
75}
76
77impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R {
78    fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> {
79        (**self).move_cursor(old, new)
80    }
81
82    fn refresh_line(
83        &mut self,
84        prompt: &str,
85        prompt_size: Position,
86        line: &LineBuffer,
87        hint: Option<String>,
88        current_row: usize,
89        old_rows: usize,
90        highlighter: Option<&dyn Highlighter>,
91    ) -> Result<(Position, Position)> {
92        (**self).refresh_line(
93            prompt,
94            prompt_size,
95            line,
96            hint,
97            current_row,
98            old_rows,
99            highlighter,
100        )
101    }
102
103    fn calculate_position(&self, s: &str, orig: Position) -> Position {
104        (**self).calculate_position(s, orig)
105    }
106
107    fn write_and_flush(&mut self, buf: &[u8]) -> Result<()> {
108        (**self).write_and_flush(buf)
109    }
110
111    fn beep(&mut self) -> Result<()> {
112        (**self).beep()
113    }
114
115    fn clear_screen(&mut self) -> Result<()> {
116        (**self).clear_screen()
117    }
118
119    fn sigwinch(&self) -> bool {
120        (**self).sigwinch()
121    }
122
123    fn update_size(&mut self) {
124        (**self).update_size()
125    }
126
127    fn get_columns(&self) -> usize {
128        (**self).get_columns()
129    }
130
131    fn get_rows(&self) -> usize {
132        (**self).get_rows()
133    }
134}
135
136/// Terminal contract
137pub trait Term {
138    type Reader: RawReader; // rl_instream
139    type Writer: Renderer; // rl_outstream
140    type Mode: RawMode;
141
142    fn new(color_mode: ColorMode, stream: OutputStreamType) -> Self;
143    /// Check if current terminal can provide a rich line-editing user
144    /// interface.
145    fn is_unsupported(&self) -> bool;
146    /// check if stdin is connected to a terminal.
147    fn is_stdin_tty(&self) -> bool;
148    /// Check if output supports colors.
149    fn colors_enabled(&self) -> bool;
150    /// Enable RAW mode for the terminal.
151    fn enable_raw_mode(&mut self) -> Result<Self::Mode>;
152    /// Create a RAW reader
153    fn create_reader(&self, config: &Config) -> Result<Self::Reader>;
154    /// Create a writer
155    fn create_writer(&self) -> Self::Writer;
156}
157
158fn truncate(text: &str, col: usize, max_col: usize) -> &str {
159    let mut col = col;
160    let mut esc_seq = 0;
161    let mut end = text.len();
162    for (i, s) in text.grapheme_indices(true) {
163        col += width(s, &mut esc_seq);
164        if col > max_col {
165            end = i;
166            break;
167        }
168    }
169    &text[..end]
170}
171
172fn width(s: &str, esc_seq: &mut u8) -> usize {
173    if *esc_seq == 1 {
174        if s == "[" {
175            // CSI
176            *esc_seq = 2;
177        } else {
178            // two-character sequence
179            *esc_seq = 0;
180        }
181        0
182    } else if *esc_seq == 2 {
183        if s == ";" || (s.as_bytes()[0] >= b'0' && s.as_bytes()[0] <= b'9') {
184        /*} else if s == "m" {
185            // last
186            *esc_seq = 0;*/
187        } else {
188            // not supported
189            *esc_seq = 0;
190        }
191        0
192    } else if s == "\x1b" {
193        *esc_seq = 1;
194        0
195    } else if s == "\n" {
196        0
197    } else {
198        s.width()
199    }
200}
201
202// If on Windows platform import Windows TTY module
203// and re-export into mod.rs scope
204#[cfg(all(windows, not(test)))]
205mod windows;
206#[cfg(all(windows, not(test)))]
207pub use self::windows::*;
208
209// If on Unix platform import Unix TTY module
210// and re-export into mod.rs scope
211#[cfg(all(unix, not(any(test, target_os = "fuchsia"))))]
212mod unix;
213#[cfg(all(unix, not(any(test, target_os = "fuchsia"))))]
214pub use self::unix::*;
215
216// If on a Fuchsia platform import Fuchsia TTY module
217// and re-export into mod.rs scope
218#[cfg(target_os = "fuchsia")]
219mod fuchsia;
220#[cfg(target_os = "fuchsia")]
221pub use self::fuchsia::*;
222
223#[cfg(test)]
224mod test;
225#[cfg(test)]
226pub use self::test::*;