rustyline/tty/
fuchsia.rs

1//! Fuchsia specific definitions
2use std::io::{self, Read, Write};
3use libc;
4use unicode_segmentation::UnicodeSegmentation;
5use utf8parse::{Parser, Receiver};
6
7use config::{ColorMode, Config, OutputStreamType};
8use highlight::Highlighter;
9use keys::{self, KeyPress};
10use line_buffer::LineBuffer;
11use error;
12use Result;
13use super::{truncate, width, Position, RawMode, RawReader, Renderer, Term};
14use StdStream;
15
16const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO;
17const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO;
18const STDERR_FILENO: libc::c_int = libc::STDERR_FILENO;
19
20fn get_win_size() -> (usize, usize) {
21    (80, 24)
22}
23
24struct StdinRaw {}
25
26impl Read for StdinRaw {
27    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
28        loop {
29            let res = unsafe {
30                libc::read(
31                    STDIN_FILENO as i32,
32                    buf.as_mut_ptr() as *mut libc::c_void,
33                    buf.len() as libc::size_t,
34                )
35            };
36            if res == -1 {
37                let error = io::Error::last_os_error();
38                if error.kind() != io::ErrorKind::Interrupted {
39                    return Err(error);
40                }
41            } else {
42                return Ok(res as usize);
43            }
44        }
45    }
46}
47
48pub type Mode = ConsoleMode;
49
50#[derive(Clone, Copy, Debug)]
51pub struct ConsoleMode {}
52
53impl RawMode for Mode {
54    /// RAW mode is never on w/ Fuchsia
55    fn disable_raw_mode(&self) -> Result<()> {
56        Ok(())
57    }
58}
59
60pub type Terminal = FuchsiaTerminal;
61
62#[derive(Clone, Debug)]
63pub struct FuchsiaTerminal {
64    #[allow(unused)]
65    unsupported: bool,
66    stdin_isatty: bool,
67    #[allow(unused)]
68    stdstream_isatty: bool,
69    pub(crate) color_mode: ColorMode,
70    stream_type: OutputStreamType,
71}
72
73struct Utf8 {
74    c: Option<char>,
75    valid: bool,
76}
77
78pub struct FuchsiaRawReader {
79    stdin: StdinRaw,
80    _timeout_ms_unused: i32,
81    buf: [u8; 1],
82    parser: Parser,
83    receiver: Utf8,
84}
85
86impl FuchsiaRawReader {
87    pub fn new(config: &Config) -> Result<FuchsiaRawReader> {
88        Ok(FuchsiaRawReader {
89            stdin: StdinRaw {},
90            _timeout_ms_unused: config.keyseq_timeout(),
91            buf: [0; 1],
92            parser: Parser::new(),
93            receiver: Utf8 {
94                c: None,
95                valid: true,
96            }
97        })
98    }
99
100    /// Handle ESC <seq1> sequences
101    fn escape_sequence(&mut self) -> Result<KeyPress> {
102        // Read the next byte representing the escape sequence.
103        let seq1 = try!(self.next_char());
104        if seq1 == '[' {
105            // ESC [ sequences. (CSI)
106            self.escape_csi()
107        } else if seq1 == 'O' {
108            // xterm
109            // ESC O sequences. (SS3)
110            self.escape_o()
111        } else if seq1 == '\x1b' {
112            // ESC ESC
113            Ok(KeyPress::Esc)
114        } else {
115            // TODO ESC-R (r): Undo all changes made to this line.
116            Ok(KeyPress::Meta(seq1))
117        }
118    }
119
120    /// Handle ESC [ <seq2> escape sequences
121    fn escape_csi(&mut self) -> Result<KeyPress> {
122        let seq2 = try!(self.next_char());
123        if seq2.is_digit(10) {
124            match seq2 {
125                '0' | '9' => {
126                    debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2);
127                    Ok(KeyPress::UnknownEscSeq)
128                }
129                _ => {
130                    // Extended escape, read additional byte.
131                    self.extended_escape(seq2)
132                }
133            }
134        } else if seq2 == '[' {
135            let seq3 = try!(self.next_char());
136            // Linux console
137            Ok(match seq3 {
138                'A' => KeyPress::F(1),
139                'B' => KeyPress::F(2),
140                'C' => KeyPress::F(3),
141                'D' => KeyPress::F(4),
142                'E' => KeyPress::F(5),
143                _ => {
144                    debug!(target: "rustyline", "unsupported esc sequence: ESC [ [ {:?}", seq3);
145                    KeyPress::UnknownEscSeq
146                }
147            })
148        } else {
149            // ANSI
150            Ok(match seq2 {
151                'A' => KeyPress::Up,    // kcuu1
152                'B' => KeyPress::Down,  // kcud1
153                'C' => KeyPress::Right, // kcuf1
154                'D' => KeyPress::Left,  // kcub1
155                'F' => KeyPress::End,
156                'H' => KeyPress::Home, // khome
157                'Z' => KeyPress::BackTab,
158                _ => {
159                    debug!(target: "rustyline", "unsupported esc sequence: ESC [ {:?}", seq2);
160                    KeyPress::UnknownEscSeq
161                }
162            })
163        }
164    }
165
166    /// Handle ESC [ <seq2:digit> escape sequences
167    fn extended_escape(&mut self, seq2: char) -> Result<KeyPress> {
168        let seq3 = try!(self.next_char());
169        if seq3 == '~' {
170            Ok(match seq2 {
171                '1' | '7' => KeyPress::Home, // tmux, xrvt
172                '2' => KeyPress::Insert,
173                '3' => KeyPress::Delete,    // kdch1
174                '4' | '8' => KeyPress::End, // tmux, xrvt
175                '5' => KeyPress::PageUp,    // kpp
176                '6' => KeyPress::PageDown,  // knp
177                _ => {
178                    debug!(target: "rustyline",
179                           "unsupported esc sequence: ESC [ {} ~", seq2);
180                    KeyPress::UnknownEscSeq
181                }
182            })
183        } else if seq3.is_digit(10) {
184            let seq4 = try!(self.next_char());
185            if seq4 == '~' {
186                Ok(match (seq2, seq3) {
187                    ('1', '1') => KeyPress::F(1),  // rxvt-unicode
188                    ('1', '2') => KeyPress::F(2),  // rxvt-unicode
189                    ('1', '3') => KeyPress::F(3),  // rxvt-unicode
190                    ('1', '4') => KeyPress::F(4),  // rxvt-unicode
191                    ('1', '5') => KeyPress::F(5),  // kf5
192                    ('1', '7') => KeyPress::F(6),  // kf6
193                    ('1', '8') => KeyPress::F(7),  // kf7
194                    ('1', '9') => KeyPress::F(8),  // kf8
195                    ('2', '0') => KeyPress::F(9),  // kf9
196                    ('2', '1') => KeyPress::F(10), // kf10
197                    ('2', '3') => KeyPress::F(11), // kf11
198                    ('2', '4') => KeyPress::F(12), // kf12
199                    _ => {
200                        debug!(target: "rustyline",
201                               "unsupported esc sequence: ESC [ {}{} ~", seq2, seq3);
202                        KeyPress::UnknownEscSeq
203                    }
204                })
205            } else if seq4 == ';' {
206                let seq5 = try!(self.next_char());
207                if seq5.is_digit(10) {
208                    let seq6 = try!(self.next_char()); // '~' expected
209                    debug!(target: "rustyline",
210                           "unsupported esc sequence: ESC [ {}{} ; {} {}", seq2, seq3, seq5, seq6);
211                } else {
212                    debug!(target: "rustyline",
213                           "unsupported esc sequence: ESC [ {}{} ; {:?}", seq2, seq3, seq5);
214                }
215                Ok(KeyPress::UnknownEscSeq)
216            } else {
217                debug!(target: "rustyline",
218                       "unsupported esc sequence: ESC [ {}{} {:?}", seq2, seq3, seq4);
219                Ok(KeyPress::UnknownEscSeq)
220            }
221        } else if seq3 == ';' {
222            let seq4 = try!(self.next_char());
223            if seq4.is_digit(10) {
224                let seq5 = try!(self.next_char());
225                if seq2 == '1' {
226                    Ok(match (seq4, seq5) {
227                        ('5', 'A') => KeyPress::ControlUp,
228                        ('5', 'B') => KeyPress::ControlDown,
229                        ('5', 'C') => KeyPress::ControlRight,
230                        ('5', 'D') => KeyPress::ControlLeft,
231                        ('2', 'A') => KeyPress::ShiftUp,
232                        ('2', 'B') => KeyPress::ShiftDown,
233                        ('2', 'C') => KeyPress::ShiftRight,
234                        ('2', 'D') => KeyPress::ShiftLeft,
235                        _ => {
236                            debug!(target: "rustyline",
237                                   "unsupported esc sequence: ESC [ 1 ; {} {:?}", seq4, seq5);
238                            KeyPress::UnknownEscSeq
239                        }
240                    })
241                } else {
242                    debug!(target: "rustyline",
243                           "unsupported esc sequence: ESC [ {} ; {} {:?}", seq2, seq4, seq5);
244                    Ok(KeyPress::UnknownEscSeq)
245                }
246            } else {
247                debug!(target: "rustyline",
248                       "unsupported esc sequence: ESC [ {} ; {:?}", seq2, seq4);
249                Ok(KeyPress::UnknownEscSeq)
250            }
251        } else {
252            Ok(match (seq2, seq3) {
253                ('5', 'A') => KeyPress::ControlUp,
254                ('5', 'B') => KeyPress::ControlDown,
255                ('5', 'C') => KeyPress::ControlRight,
256                ('5', 'D') => KeyPress::ControlLeft,
257                _ => {
258                    debug!(target: "rustyline",
259                           "unsupported esc sequence: ESC [ {} {:?}", seq2, seq3);
260                    KeyPress::UnknownEscSeq
261                }
262            })
263        }
264    }
265
266    /// Handle ESC O <seq2> escape sequences
267    fn escape_o(&mut self) -> Result<KeyPress> {
268        let seq2 = try!(self.next_char());
269        Ok(match seq2 {
270            'A' => KeyPress::Up,    // kcuu1
271            'B' => KeyPress::Down,  // kcud1
272            'C' => KeyPress::Right, // kcuf1
273            'D' => KeyPress::Left,  // kcub1
274            'F' => KeyPress::End,   // kend
275            'H' => KeyPress::Home,  // khome
276            'P' => KeyPress::F(1),  // kf1
277            'Q' => KeyPress::F(2),  // kf2
278            'R' => KeyPress::F(3),  // kf3
279            'S' => KeyPress::F(4),  // kf4
280            'a' => KeyPress::ControlUp,
281            'b' => KeyPress::ControlDown,
282            'c' => KeyPress::ControlRight, // rxvt
283            'd' => KeyPress::ControlLeft,  // rxvt
284            _ => {
285                debug!(target: "rustyline", "unsupported esc sequence: ESC O {:?}", seq2);
286                KeyPress::UnknownEscSeq
287            }
288        })
289    }
290}
291
292pub struct FuchsiaRenderer {
293    out: StdStream,
294    cols: usize,
295    buffer: String,
296}
297
298impl FuchsiaRenderer {
299    pub fn new(stream_type: OutputStreamType) -> FuchsiaRenderer {
300        let out = StdStream::from_stream_type(stream_type);
301        let (cols, _) = get_win_size();
302        FuchsiaRenderer {
303            out,
304            cols,
305            buffer: String::with_capacity(1024),
306        }
307    }
308}
309
310impl Renderer for FuchsiaRenderer {
311    fn move_cursor(&mut self, old: Position, new: Position) -> Result<()> {
312        use std::fmt::Write;
313        let mut ab = String::new();
314        if new.row > old.row {
315            // move down
316            let row_shift = new.row - old.row;
317            if row_shift == 1 {
318                ab.push_str("\x1b[B");
319            } else {
320                write!(ab, "\x1b[{}B", row_shift).unwrap();
321            }
322        } else if new.row < old.row {
323            // move up
324            let row_shift = old.row - new.row;
325            if row_shift == 1 {
326                ab.push_str("\x1b[A");
327            } else {
328                write!(ab, "\x1b[{}A", row_shift).unwrap();
329            }
330        }
331        if new.col > old.col {
332            // move right
333            let col_shift = new.col - old.col;
334            if col_shift == 1 {
335                ab.push_str("\x1b[C");
336            } else {
337                write!(ab, "\x1b[{}C", col_shift).unwrap();
338            }
339        } else if new.col < old.col {
340            // move left
341            let col_shift = old.col - new.col;
342            if col_shift == 1 {
343                ab.push_str("\x1b[D");
344            } else {
345                write!(ab, "\x1b[{}D", col_shift).unwrap();
346            }
347        }
348        self.write_and_flush(ab.as_bytes())
349    }
350
351    /// Display `prompt`, line and cursor in terminal output
352    fn refresh_line(
353        &mut self,
354        prompt: &str,
355        prompt_size: Position,
356        line: &LineBuffer,
357        hint: Option<String>,
358        current_row: usize,
359        old_rows: usize,
360        highlighter: Option<&dyn Highlighter>,
361    ) -> Result<(Position, Position)> {
362        use std::fmt::Write;
363        self.buffer.clear();
364
365        // calculate the position of the end of the input line
366        let end_pos = self.calculate_position(line, prompt_size);
367        // calculate the desired position of the cursor
368        let cursor = self.calculate_position(&line[..line.pos()], prompt_size);
369
370        // self.old_rows < self.cursor.row if the prompt spans multiple lines and if
371        // this is the default State.
372        let cursor_row_movement = old_rows.checked_sub(current_row).unwrap_or(0);
373        // move the cursor down as required
374        if cursor_row_movement > 0 {
375            write!(self.buffer, "\x1b[{}B", cursor_row_movement).unwrap();
376        }
377        // clear old rows
378        for _ in 0..old_rows {
379            self.buffer.push_str("\r\x1b[0K\x1b[A");
380        }
381        // clear the line
382        self.buffer.push_str("\r\x1b[0K");
383
384        if let Some(highlighter) = highlighter {
385            // display the prompt
386            self.buffer.push_str(&highlighter.highlight_prompt(prompt));
387            // display the input line
388            self.buffer
389                .push_str(&highlighter.highlight(line, line.pos()));
390        } else {
391            // display the prompt
392            self.buffer.push_str(prompt);
393            // display the input line
394            self.buffer.push_str(line);
395        }
396        // display hint
397        if let Some(hint) = hint {
398            let truncate = truncate(&hint, end_pos.col, self.cols);
399            if let Some(highlighter) = highlighter {
400                self.buffer.push_str(&highlighter.highlight_hint(truncate));
401            } else {
402                self.buffer.push_str(truncate);
403            }
404        }
405        // we have to generate our own newline on line wrap
406        if end_pos.col == 0 && end_pos.row > 0 {
407            self.buffer.push_str("\n");
408        }
409        // position the cursor
410        let cursor_row_movement = end_pos.row - cursor.row;
411        // move the cursor up as required
412        if cursor_row_movement > 0 {
413            write!(self.buffer, "\x1b[{}A", cursor_row_movement).unwrap();
414        }
415        // position the cursor within the line
416        if cursor.col > 0 {
417            write!(self.buffer, "\r\x1b[{}C", cursor.col).unwrap();
418        } else {
419            self.buffer.push('\r');
420        }
421
422        try!(self.out.write_all(self.buffer.as_bytes()));
423        try!(self.out.flush());
424        Ok((cursor, end_pos))
425    }
426
427    /// Calculate the number of columns and rows used to display `s` on a
428    /// `cols` width terminal starting at `orig`.
429    fn calculate_position(&self, s: &str, orig: Position) -> Position {
430        let mut pos = orig;
431        let mut esc_seq = 0;
432        for c in s.graphemes(true) {
433            if c == "\n" {
434                pos.row += 1;
435                pos.col = 0;
436                continue;
437            }
438            let cw = width(c, &mut esc_seq);
439            pos.col += cw;
440            if pos.col > self.cols {
441                pos.row += 1;
442                pos.col = cw;
443            }
444        }
445        if pos.col == self.cols {
446            pos.col = 0;
447            pos.row += 1;
448        }
449        pos
450    }
451
452    fn write_and_flush(&mut self, buf: &[u8]) -> Result<()> {
453        try!(self.out.write_all(buf));
454        try!(self.out.flush());
455        Ok(())
456    }
457
458    /// Clear the screen. Used to handle ctrl+l
459    fn clear_screen(&mut self) -> Result<()> {
460        self.write_and_flush(b"\x1b[H\x1b[2J")
461    }
462
463    /// Check if a SIGWINCH signal has been received
464    fn sigwinch(&self) -> bool {
465        false
466    }
467
468    /// Update the number of columns/rows in the current terminal.
469    fn update_size(&mut self) {
470        let (cols, _) = get_win_size();
471        self.cols = cols;
472    }
473
474    /// Get the number of columns in the current terminal.
475    fn get_columns(&self) -> usize {
476        let (cols, _) = get_win_size();
477        cols
478    }
479
480    /// Get the number of rows in the current terminal.
481    fn get_rows(&self) -> usize {
482        let (_, rows) = get_win_size();
483        rows
484    }
485}
486
487
488// TODO properly set raw mode, handle escape key timeouts
489
490impl RawReader for FuchsiaRawReader {
491    fn next_key(&mut self, _single_esc_abort: bool) -> Result<KeyPress> {
492        let c = try!(self.next_char());
493
494        let key = keys::char_to_key_press(c);
495        if key == KeyPress::Esc {
496            return self.escape_sequence();
497        }
498
499        Ok(key)
500    }
501
502    fn next_char(&mut self) -> Result<char> {
503        loop {
504            let n = try!(self.stdin.read(&mut self.buf));
505            if n == 0 {
506                return Err(error::ReadlineError::Eof);
507            }
508            let b = self.buf[0];
509            self.parser.advance(&mut self.receiver, b);
510            if !self.receiver.valid {
511                return Err(error::ReadlineError::Utf8Error);
512            } else if self.receiver.c.is_some() {
513                return Ok(self.receiver.c.take().unwrap());
514            }
515        }
516    }
517}
518
519impl Receiver for Utf8 {
520    /// Called whenever a codepoint is parsed successfully
521    fn codepoint(&mut self, c: char) {
522        self.c = Some(c);
523        self.valid = true;
524    }
525
526    /// Called when an invalid_sequence is detected
527    fn invalid_sequence(&mut self) {
528        self.c = None;
529        self.valid = false;
530    }
531}
532
533fn is_a_tty(fd: libc::c_int) -> bool {
534    unsafe { libc::isatty(fd) != 0 }
535}
536
537impl Term for FuchsiaTerminal {
538    type Reader = FuchsiaRawReader;
539    type Writer = FuchsiaRenderer;
540    type Mode = Mode;
541
542    fn new(color_mode: ColorMode, stream_type: OutputStreamType) -> FuchsiaTerminal {
543        FuchsiaTerminal {
544            unsupported: false,
545            stdin_isatty: is_a_tty(STDIN_FILENO as i32),
546            stdstream_isatty: is_a_tty(if stream_type == OutputStreamType::Stdout {
547                STDOUT_FILENO as i32
548            } else {
549                STDERR_FILENO as i32
550            }),
551            color_mode,
552            stream_type,
553        }
554    }
555    /// Check if current terminal can provide a rich line-editing user
556    /// interface.
557    fn is_unsupported(&self) -> bool {
558        false
559    }
560    /// check if stdin is connected to a terminal.
561    fn is_stdin_tty(&self) -> bool {
562        self.stdin_isatty
563    }
564    /// Check if output supports colors.
565    fn colors_enabled(&self) -> bool {
566        true
567    }
568    /// Enable RAW mode for the terminal.
569    fn enable_raw_mode(&mut self) -> Result<Self::Mode> {
570        Ok(Mode {})
571    }
572    /// Create a RAW reader
573    fn create_reader(&self, config: &Config) -> Result<Self::Reader> {
574        FuchsiaRawReader::new(config)
575    }
576    /// Create a writer
577    fn create_writer(&self) -> Self::Writer {
578        FuchsiaRenderer::new(self.stream_type)
579    }
580}