diff --git a/Cargo.toml b/Cargo.toml index ec6586afc44d94d7e6dfe4a3c44b571317c1689f..c35c55d57c7854dde462b3740b7391db2e2b10e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ categories = ["command-line-interface"] travis-ci = { repository = "kkawakam/rustyline" } appveyor = { repository = "kkawakam/rustyline" } + [dependencies] libc = "0.2.7" log = "0.3" @@ -21,9 +22,13 @@ unicode-width = "0.1.3" unicode-segmentation = "1.0" encode_unicode = "0.1.3" -[target.'cfg(unix)'.dependencies] +[target.'cfg(all(unix, not(any(target_os = "fuchsia"))))'.dependencies] nix = "0.8" +[target.'cfg(target_os = "fuchsia")'.dependencies] +fuchsia-zircon = "0.3.2" +fuchsia-device = "0.1.0" + [target.'cfg(windows)'.dependencies] winapi = "0.2" kernel32-sys = "0.2" diff --git a/src/error.rs b/src/error.rs index 009ec65f1dae2bfdf966bd63d1392c6c8267f926..d778960f17306f3625eb79ecdf4fc72bfe953070 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,7 +4,7 @@ use std::char; use std::io; use std::error; use std::fmt; -#[cfg(unix)] +#[cfg(all(unix, not(any(target_os = "fuchsia"))))] use nix; #[cfg(unix)] @@ -24,7 +24,7 @@ pub enum ReadlineError { #[cfg(unix)] Char(char_iter::CharsError), /// Unix Error from syscall - #[cfg(unix)] + #[cfg(all(unix, not(any(target_os = "fuchsia"))))] Errno(nix::Error), #[cfg(windows)] WindowResize, @@ -40,7 +40,7 @@ impl fmt::Display for ReadlineError { ReadlineError::Interrupted => write!(f, "Interrupted"), #[cfg(unix)] ReadlineError::Char(ref err) => err.fmt(f), - #[cfg(unix)] + #[cfg(all(unix, not(any(target_os = "fuchsia"))))] ReadlineError::Errno(ref err) => write!(f, "Errno: {}", err.errno().desc()), #[cfg(windows)] ReadlineError::WindowResize => write!(f, "WindowResize"), @@ -58,7 +58,7 @@ impl error::Error for ReadlineError { ReadlineError::Interrupted => "Interrupted", #[cfg(unix)] ReadlineError::Char(ref err) => err.description(), - #[cfg(unix)] + #[cfg(all(unix, not(any(target_os = "fuchsia"))))] ReadlineError::Errno(ref err) => err.errno().desc(), #[cfg(windows)] ReadlineError::WindowResize => "WindowResize", @@ -74,7 +74,7 @@ impl From<io::Error> for ReadlineError { } } -#[cfg(unix)] +#[cfg(all(unix, not(any(target_os = "fuchsia"))))] impl From<nix::Error> for ReadlineError { fn from(err: nix::Error) -> ReadlineError { ReadlineError::Errno(err) diff --git a/src/lib.rs b/src/lib.rs index 24829e08500de1e2091e73f24008e2cce9a5bd2e..133b569e6ac96cec6ffd22a1f3304d18536e415e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,12 +22,16 @@ extern crate encode_unicode; extern crate log; extern crate unicode_segmentation; extern crate unicode_width; -#[cfg(unix)] +#[cfg(all(unix, not(any(target_os = "fuchsia"))))] extern crate nix; #[cfg(windows)] extern crate winapi; #[cfg(windows)] extern crate kernel32; +#[cfg(target_os = "fuchsia")] +extern crate fuchsia_zircon as zx; +#[cfg(target_os = "fuchsia")] +extern crate fuchsia_device; pub mod completion; mod consts; @@ -1089,7 +1093,7 @@ fn readline_edit<C: Completer>(prompt: &str, Cmd::Interrupt => { return Err(error::ReadlineError::Interrupted); } - #[cfg(unix)] + #[cfg(all(unix, not(any(target_os = "fuchsia"))))] Cmd::Suspend => { try!(original_mode.disable_raw_mode()); try!(tty::suspend()); diff --git a/src/tty/fuchsia.rs b/src/tty/fuchsia.rs new file mode 100644 index 0000000000000000000000000000000000000000..a63723b1cb5a173263848bfb11d8dfc6f0a9f02d --- /dev/null +++ b/src/tty/fuchsia.rs @@ -0,0 +1,244 @@ +//! Fuchsia specific definitions +use std::io::{self, Read, Stdout, Write}; +use std::mem; +use std::sync::atomic; +use libc; +use char_iter; + +use config::Config; +use consts::{self, KeyPress}; +use error; +use Result; +use super::{RawMode, RawReader, Term}; +use fuchsia_device::pty; + +const STDIN_FILENO: usize = 0; +const STDOUT_FILENO: usize = 1; + +fn get_win_size() -> (usize, usize) { + match pty::get_window_size() { + Ok(size) => { + (size.width as usize, size.height as usize) + } + _ => (80, 24), + } +} + +struct StdinRaw {} + +impl Read for StdinRaw { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + loop { + let res = unsafe { + libc::read( + STDIN_FILENO as i32, + buf.as_mut_ptr() as *mut libc::c_void, + buf.len() as libc::size_t, + ) + }; + if res == -1 { + let error = io::Error::last_os_error(); + if error.kind() != io::ErrorKind::Interrupted { + return Err(error); + } + } else { + return Ok(res as usize); + } + } + } +} + +pub type Mode = ConsoleMode; + +#[derive(Clone, Copy, Debug)] +pub struct ConsoleMode {} + +impl RawMode for Mode { + /// RAW mode is never on w/ Fuchsia + fn disable_raw_mode(&self) -> Result<()> { + Ok(()) + } +} + +pub type Terminal = Console; + +#[derive(Clone, Debug)] +pub struct Console { + stdin_isatty: bool, +} + +pub struct FuchsiaRawReader { + chars: char_iter::Chars<StdinRaw>, +} + +impl FuchsiaRawReader { + pub fn new() -> Result<FuchsiaRawReader> { + let stdin = StdinRaw {}; + Ok(FuchsiaRawReader { + chars: char_iter::chars(stdin), + }) + } + fn escape_sequence(&mut self) -> Result<KeyPress> { + // Read the next two bytes representing the escape sequence. + let seq1 = try!(self.next_char()); + if seq1 == '[' { + // ESC [ sequences. + let seq2 = try!(self.next_char()); + if seq2.is_digit(10) { + // Extended escape, read additional byte. + let seq3 = try!(self.next_char()); + if seq3 == '~' { + Ok(match seq2 { + '1' | '7' => KeyPress::Home, // '1': xterm + '3' => KeyPress::Delete, + '4' | '8' => KeyPress::End, // '4': xterm + '5' => KeyPress::PageUp, + '6' => KeyPress::PageDown, + _ => { + debug!(target: "rustyline", "unsupported esc sequence: ESC{:?}{:?}{:?}", seq1, seq2, seq3); + KeyPress::UnknownEscSeq + } + }) + } else { + debug!(target: "rustyline", "unsupported esc sequence: ESC{:?}{:?}{:?}", seq1, seq2, seq3); + Ok(KeyPress::UnknownEscSeq) + } + } else { + Ok(match seq2 { + 'A' => KeyPress::Up, // ANSI + 'B' => KeyPress::Down, + 'C' => KeyPress::Right, + 'D' => KeyPress::Left, + 'F' => KeyPress::End, + 'H' => KeyPress::Home, + _ => { + debug!(target: "rustyline", "unsupported esc sequence: ESC{:?}{:?}", seq1, seq2); + KeyPress::UnknownEscSeq + } + }) + } + } else if seq1 == 'O' { + // ESC O sequences. + let seq2 = try!(self.next_char()); + Ok(match seq2 { + 'A' => KeyPress::Up, + 'B' => KeyPress::Down, + 'C' => KeyPress::Right, + 'D' => KeyPress::Left, + 'F' => KeyPress::End, + 'H' => KeyPress::Home, + _ => { + debug!(target: "rustyline", "unsupported esc sequence: ESC{:?}{:?}", seq1, seq2); + KeyPress::UnknownEscSeq + } + }) + } else { + // TODO ESC-R (r): Undo all changes made to this line. + Ok(match seq1 { + '\x08' => KeyPress::Meta('\x08'), // Backspace + '-' => KeyPress::Meta('-'), + '0'...'9' => KeyPress::Meta(seq1), + '<' => KeyPress::Meta('<'), + '>' => KeyPress::Meta('>'), + 'b' | 'B' => KeyPress::Meta('B'), + 'c' | 'C' => KeyPress::Meta('C'), + 'd' | 'D' => KeyPress::Meta('D'), + 'f' | 'F' => KeyPress::Meta('F'), + 'l' | 'L' => KeyPress::Meta('L'), + 'n' | 'N' => KeyPress::Meta('N'), + 'p' | 'P' => KeyPress::Meta('P'), + 't' | 'T' => KeyPress::Meta('T'), + 'u' | 'U' => KeyPress::Meta('U'), + 'y' | 'Y' => KeyPress::Meta('Y'), + '\x7f' => KeyPress::Meta('\x7f'), // Delete + _ => { + debug!(target: "rustyline", "unsupported esc sequence: M-{:?}", seq1); + KeyPress::UnknownEscSeq + } + }) + } + } +} + +// TODO properly set raw mode, process escape keys + +impl RawReader for FuchsiaRawReader { + fn next_key(&mut self) -> Result<KeyPress> { + let c = try!(self.next_char()); + + let mut key = consts::char_to_key_press(c); + if key == KeyPress::Esc { + // TODO + debug!(target: "rustyline", "ESC + {:?} currently unsupported", key); + } + + Ok(key) + } + + fn next_char(&mut self) -> Result<char> { + match self.chars.next() { + Some(ch) => Ok(ch?), + None => Err(error::ReadlineError::Eof), + } + } +} + +impl Term for Console { + type Reader = FuchsiaRawReader; + type Writer = Stdout; + type Mode = Mode; + + fn new() -> Console { + let stdin_isatty = true; + Console { + stdin_isatty: stdin_isatty, + } + } + + /// Checking for an unsupported TERM in fuchsia is a no-op + fn is_unsupported(&self) -> bool { + false + } + + fn is_stdin_tty(&self) -> bool { + self.stdin_isatty + } + + /// Try to get the number of columns in the current terminal, + /// or assume 80 if it fails. + fn get_columns(&self) -> usize { + let (cols, _) = get_win_size(); + cols + } + + /// Try to get the number of rows in the current terminal, + /// or assume 24 if it fails. + fn get_rows(&self) -> usize { + let (_, rows) = get_win_size(); + rows + } + + /// Enable RAW mode for the terminal. No termios support, so this fakes it + fn enable_raw_mode(&self) -> Result<Mode> { + Ok(Mode {}) + } + + fn create_reader(&self, _: &Config) -> Result<FuchsiaRawReader> { + FuchsiaRawReader::new() + } + + fn create_writer(&self) -> Stdout { + io::stdout() + } + + fn sigwinch(&self) -> bool { + false + } + + /// Clear the screen. Used to handle ctrl+l + fn clear_screen(&mut self, w: &mut Write) -> Result<()> { + try!(w.write_all(b"\x1b[H\x1b[2J")); + try!(w.flush()); + Ok(()) + } +} diff --git a/src/tty/mod.rs b/src/tty/mod.rs index fe3ac99b5153e1477b31798fb56b0b6bf8448a0f..f7ce05114809c83570bfd118a172eebb2d356a69 100644 --- a/src/tty/mod.rs +++ b/src/tty/mod.rs @@ -55,11 +55,18 @@ pub use self::windows::*; // If on Unix platform import Unix TTY module // and re-export into mod.rs scope -#[cfg(all(unix, not(test)))] +#[cfg(all(unix, not(any(test, target_os = "fuchsia"))))] mod unix; -#[cfg(all(unix, not(test)))] +#[cfg(all(unix, not(any(test, target_os = "fuchsia"))))] pub use self::unix::*; +// If on a Fuchsia platform import Fuchsia TTY module +// and re-export into mod.rs scope +#[cfg(target_os = "fuchsia")] +mod fuchsia; +#[cfg(target_os = "fuchsia")] +pub use self::fuchsia::*; + #[cfg(test)] mod test; #[cfg(test)]