diff --git a/Cargo.toml b/Cargo.toml index 3580e38803f536151ff7897cb3f0e33f9808e7fb..e49e3c0f1966c66c573562c4cee4b55970e69b47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ unicode-width = "0.1.3" [target.'cfg(unix)'.dependencies] nix = "0.5.0" +term = "0.4" [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/examples/example.rs b/examples/example.rs index 8d49697fa64fddfeb4bf5025b3dac9b9d7cdbcc4..ff7f9b625be93d2dd0c9de5c51c4b116f938f8a9 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -19,7 +19,7 @@ fn main() { .completion_type(CompletionType::List) .build(); let c = FilenameCompleter::new(); - let mut rl = Editor::new(config); + let mut rl = Editor::new(config).expect("Cannot create line editor"); rl.set_completer(Some(c)); if let Err(_) = rl.load_history("history.txt") { println!("No previous history."); diff --git a/rustfmt.toml b/rustfmt.toml index 44148a2d3c3ed6bc75e28e1c55eec63f4d720698..6496add0b747be108c2a26d5848d6604179d3a12 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,3 @@ -reorder_imports = true +reorder_imports = false +normalise_comments = false +write_mode = "Overwrite" \ No newline at end of file diff --git a/src/error.rs b/src/error.rs index 3df6e70e3fccc3b3b098012e317d01df1760014a..1a4d92940bf872941b7acbc03efbe676acb29fad 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,8 @@ use std::error; use std::fmt; #[cfg(unix)] use nix; +#[cfg(unix)] +use term; /// The error type for Rustyline errors that can arise from /// I/O related errors or Errno when using the nix-rust library @@ -23,6 +25,8 @@ pub enum ReadlineError { /// Unix Error from syscall #[cfg(unix)] Errno(nix::Error), + #[cfg(unix)] + TermError(term::Error), #[cfg(windows)] WindowResize, #[cfg(windows)] @@ -39,6 +43,8 @@ impl fmt::Display for ReadlineError { ReadlineError::Char(ref err) => err.fmt(f), #[cfg(unix)] ReadlineError::Errno(ref err) => write!(f, "Errno: {}", err.errno().desc()), + #[cfg(unix)] + ReadlineError::TermError(ref err) => err.fmt(f), #[cfg(windows)] ReadlineError::WindowResize => write!(f, "WindowResize"), #[cfg(windows)] @@ -57,6 +63,8 @@ impl error::Error for ReadlineError { ReadlineError::Char(ref err) => err.description(), #[cfg(unix)] ReadlineError::Errno(ref err) => err.errno().desc(), + #[cfg(unix)] + ReadlineError::TermError(ref err) => err.description(), #[cfg(windows)] ReadlineError::WindowResize => "WindowResize", #[cfg(windows)] @@ -85,6 +93,13 @@ impl From<io::CharsError> for ReadlineError { } } +#[cfg(unix)] +impl From<term::Error> for ReadlineError { + fn from(err: term::Error) -> ReadlineError { + ReadlineError::TermError(err) + } +} + #[cfg(windows)] impl From<char::DecodeUtf16Error> for ReadlineError { fn from(err: char::DecodeUtf16Error) -> ReadlineError { diff --git a/src/lib.rs b/src/lib.rs index 9971fae23502f0a987461831de843c06c8887c20..a9cc263f6b20c589ac3a16296e0640fab80d4599 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! //! ``` //! let config = rustyline::Config::default(); -//! let mut rl = rustyline::Editor::<()>::new(config); +//! let mut rl = rustyline::Editor::<()>::new(config).expect("Cannot create line editor"); //! let readline = rl.readline(">> "); //! match readline { //! Ok(line) => println!("Line: {:?}",line), @@ -22,6 +22,8 @@ extern crate libc; #[cfg(unix)] extern crate nix; +#[cfg(unix)] +extern crate term; extern crate unicode_width; #[cfg(windows)] extern crate winapi; @@ -38,12 +40,10 @@ pub mod config; mod tty; -use std::cell::RefCell; use std::fmt; use std::io::{self, Read, Write}; use std::mem; use std::path::Path; -use std::rc::Rc; use std::result; #[cfg(unix)] use nix::sys::signal; @@ -70,7 +70,7 @@ struct State<'out, 'prompt> { old_rows: usize, // Number of rows used so far (from start of prompt to end of input) history_index: usize, // The history index we are currently editing snapshot: LineBuffer, // Current edited line before history browsing/completion - term: Rc<RefCell<Terminal>>, // terminal + term: Terminal, // terminal } #[derive(Copy, Clone, Debug, Default)] @@ -81,12 +81,12 @@ struct Position { impl<'out, 'prompt> State<'out, 'prompt> { fn new(out: &'out mut Write, - term: Rc<RefCell<Terminal>>, + term: Terminal, prompt: &'prompt str, history_index: usize) -> State<'out, 'prompt> { let capacity = MAX_LINE; - let cols = term.borrow().get_columns(); + let cols = term.get_columns(); let prompt_size = calculate_position(prompt, Position::default(), cols); State { out: out, @@ -179,16 +179,15 @@ impl<'out, 'prompt> State<'out, 'prompt> { // calculate the desired position of the cursor let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols); - let mut term = self.term.borrow_mut(); // position at the start of the prompt, clear to end of previous input - let mut info = try!(term.get_console_screen_buffer_info()); + let mut info = try!(self.term.get_console_screen_buffer_info()); info.dwCursorPosition.X = 0; info.dwCursorPosition.Y -= self.cursor.row as i16; - try!(term.set_console_cursor_position(info.dwCursorPosition)); + try!(self.term.set_console_cursor_position(info.dwCursorPosition)); let mut _count = 0; - try!(term.fill_console_output_character( - (info.dwSize.X * (self.old_rows as i16 +1)) as u32, - info.dwCursorPosition)); + try!(self.term + .fill_console_output_character((info.dwSize.X * (self.old_rows as i16 + 1)) as u32, + info.dwCursorPosition)); let mut ab = String::new(); // display the prompt ab.push_str(prompt); // TODO handle ansi escape code (SetConsoleTextAttribute) @@ -197,10 +196,10 @@ impl<'out, 'prompt> State<'out, 'prompt> { try!(write_and_flush(self.out, ab.as_bytes())); // position the cursor - let mut info = try!(term.get_console_screen_buffer_info()); + let mut info = try!(self.term.get_console_screen_buffer_info()); info.dwCursorPosition.X = cursor.col as i16; info.dwCursorPosition.Y -= (end_pos.row - cursor.row) as i16; - try!(term.set_console_cursor_position(info.dwCursorPosition)); + try!(self.term.set_console_cursor_position(info.dwCursorPosition)); self.cursor = cursor; self.old_rows = end_pos.row; @@ -209,7 +208,7 @@ impl<'out, 'prompt> State<'out, 'prompt> { } fn update_columns(&mut self) { - self.cols = self.term.borrow().get_columns(); + self.cols = self.term.get_columns(); } } @@ -644,7 +643,7 @@ fn page_completions<R: Read>(rdr: &mut tty::RawReader<R>, .unwrap() + min_col_pad); let num_cols = s.cols / max_width; - let mut pause_row = s.term.borrow().get_rows() - 1; + let mut pause_row = s.term.get_rows() - 1; let num_rows = (candidates.len() + num_cols - 1) / num_cols; let mut ab = String::new(); for row in 0..num_rows { @@ -663,7 +662,7 @@ fn page_completions<R: Read>(rdr: &mut tty::RawReader<R>, KeyPress::Char('y') | KeyPress::Char('Y') | KeyPress::Char(' ') => { - pause_row += s.term.borrow().get_rows() - 1; + pause_row += s.term.get_rows() - 1; } KeyPress::Enter => { pause_row += 1; @@ -785,14 +784,17 @@ fn readline_edit<C: Completer>(prompt: &str, let mut stdout = io::stdout(); editor.kill_ring.reset(); - let mut s = State::new(&mut stdout, editor.term.clone(), prompt, editor.history.len()); + let mut s = State::new(&mut stdout, + editor.term.clone(), + prompt, + editor.history.len()); try!(s.refresh_line()); - let mut rdr = try!(s.term.borrow().create_reader()); + let mut rdr = try!(s.term.create_reader()); loop { let rk = rdr.next_key(true); - if rk.is_err() && s.term.borrow().sigwinch() { + if rk.is_err() && s.term.sigwinch() { s.update_columns(); try!(s.refresh_line()); continue; @@ -881,7 +883,7 @@ fn readline_edit<C: Completer>(prompt: &str, } KeyPress::Ctrl('L') => { // Clear the screen leaving the current line at the top of the screen. - try!(s.term.borrow_mut().clear_screen(&mut s.out)); + try!(s.term.clear_screen(&mut s.out)); try!(s.refresh_line()) } KeyPress::Ctrl('N') | @@ -928,9 +930,9 @@ fn readline_edit<C: Completer>(prompt: &str, } #[cfg(unix)] KeyPress::Ctrl('Z') => { - try!(s.term.borrow_mut().disable_raw_mode(original_mode)); + try!(tty::disable_raw_mode(original_mode)); try!(signal::raise(signal::SIGSTOP)); - try!(s.term.borrow_mut().enable_raw_mode()); // TODO original_mode may have changed + try!(tty::enable_raw_mode()); // TODO original_mode may have changed try!(s.refresh_line()) } // TODO CTRL-_ // undo @@ -1047,7 +1049,7 @@ fn readline_direct() -> Result<String> { /// Line editor pub struct Editor<C: Completer> { - term: Rc<RefCell<Terminal>>, + term: Terminal, history: History, completer: Option<C>, kill_ring: KillRing, @@ -1055,26 +1057,26 @@ pub struct Editor<C: Completer> { } impl<C: Completer> Editor<C> { - pub fn new(config: Config) -> Editor<C> { - let term = Rc::new(RefCell::new(Terminal::new())); - Editor { + pub fn new(config: Config) -> Result<Editor<C>> { + let term = try!(Terminal::new()); + Ok(Editor { term: term, history: History::new(config), completer: None, kill_ring: KillRing::new(60), config: config, - } + }) } /// This method will read a line from STDIN and will display a `prompt` pub fn readline(&mut self, prompt: &str) -> Result<String> { - if self.term.borrow().is_unsupported() { + if self.term.is_unsupported() { // Write prompt and flush it to stdout let mut stdout = io::stdout(); try!(write_and_flush(&mut stdout, prompt.as_bytes())); readline_direct() - } else if !self.term.borrow().is_stdin_tty() { + } else if !self.term.is_stdin_tty() { // Not a tty: read from file / pipe. readline_direct() } else { @@ -1121,6 +1123,7 @@ impl<C: Completer> fmt::Debug for Editor<C> { #[cfg(all(unix,test))] mod test { use std::cell::RefCell; + use std::collections::HashMap; use std::io::Write; use std::rc::Rc; use line_buffer::LineBuffer; @@ -1136,7 +1139,7 @@ mod test { pos: usize, cols: usize) -> State<'out, 'static> { - let term = Rc::new(RefCell::new(Terminal::new())); + let term = Terminal::new().unwrap(); State { out: out, prompt: "", @@ -1204,7 +1207,8 @@ mod test { let mut out = ::std::io::sink(); let mut s = init_state(&mut out, "rus", 3, 80); let input = b"\n"; - let mut rdr = RawReader::new(&input[..]).unwrap(); + let terminfo_keys = Rc::new(RefCell::new(HashMap::new())); + let mut rdr = RawReader::new(&input[..], terminfo_keys).unwrap(); let completer = SimpleCompleter; let key = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap(); assert_eq!(Some(KeyPress::Ctrl('J')), key); diff --git a/src/tty/unix.rs b/src/tty/unix.rs index bf84dbb7719e9fb7f44a2e8f7cf0cee4c69a817f..b4f0ac84bab2a3b06e6678f43366bf553e0e1b8e 100644 --- a/src/tty/unix.rs +++ b/src/tty/unix.rs @@ -1,5 +1,8 @@ use std; +use std::cell::RefCell; +use std::collections::HashMap; use std::io::{Chars, Read, Write}; +use std::rc::Rc; use std::sync; use std::sync::atomic; use libc; @@ -84,6 +87,7 @@ fn is_a_tty(fd: libc::c_int) -> bool { unsafe { libc::isatty(fd) != 0 } } +/// Enable RAW mode for the terminal. pub fn enable_raw_mode() -> Result<Mode> { use nix::errno::Errno::ENOTTY; use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON, @@ -107,6 +111,7 @@ pub fn enable_raw_mode() -> Result<Mode> { Ok(original_mode) } +/// Disable RAW mode for the terminal. pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &original_mode)); Ok(()) @@ -121,11 +126,17 @@ fn clear_screen(w: &mut Write) -> Result<()> { /// Console input reader pub struct RawReader<R> { chars: Chars<R>, + terminfo_keys: Rc<RefCell<HashMap<Vec<u8>, String>>>, } impl<R: Read> RawReader<R> { - pub fn new(stdin: R) -> Result<RawReader<R>> { - Ok(RawReader { chars: stdin.chars() }) + pub fn new(stdin: R, + terminfo_keys: Rc<RefCell<HashMap<Vec<u8>, String>>>) + -> Result<RawReader<R>> { + Ok(RawReader { + chars: stdin.chars(), + terminfo_keys: terminfo_keys, + }) } // As there is no read timeout to properly handle single ESC key, @@ -230,22 +241,25 @@ extern "C" fn sigwinch_handler(_: signal::SigNum) { pub type Terminal = PosixTerminal; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct PosixTerminal { unsupported: bool, stdin_isatty: bool, + terminfo_keys: Rc<RefCell<HashMap<Vec<u8>, String>>>, } impl PosixTerminal { - pub fn new() -> PosixTerminal { - let term = PosixTerminal { + pub fn new() -> Result<PosixTerminal> { + let mut term = PosixTerminal { unsupported: is_unsupported_term(), stdin_isatty: is_a_tty(STDIN_FILENO), + terminfo_keys: Rc::new(RefCell::new(HashMap::new())), }; if !term.unsupported && term.stdin_isatty && is_a_tty(STDOUT_FILENO) { install_sigwinch_handler(); + try!(term.load_capabilities()); } - term + Ok(term) } // Init checks: @@ -262,21 +276,17 @@ impl PosixTerminal { // Init if terminal-style mode: - // pub fn load_capabilities(&mut self) -> Result<()> { - // Ok(()) - // } - - // Interactive loop: - - /// Enable RAW mode for the terminal. - pub fn enable_raw_mode(&mut self) -> Result<Mode> { - enable_raw_mode() + fn load_capabilities(&mut self) -> Result<()> { + use term::terminfo::TermInfo; + let term_info = try!(TermInfo::from_env()); + let mut terminfo_keys = self.terminfo_keys.borrow_mut(); + for (key, val) in term_info.strings.into_iter() { + terminfo_keys.insert(val.clone(), key.clone()); + } + Ok(()) } - /// Disable RAW mode for the terminal. - pub fn disable_raw_mode(&mut self, mode: Mode) -> Result<()> { - disable_raw_mode(mode) - } + // Interactive loop: /// Get the number of columns in the current terminal. pub fn get_columns(&self) -> usize { @@ -290,7 +300,7 @@ impl PosixTerminal { /// Create a RAW reader pub fn create_reader(&self) -> Result<RawReader<std::io::Stdin>> { - RawReader::new(std::io::stdin()) + RawReader::new(std::io::stdin(), self.terminfo_keys.clone()) } /// Check if a SIGWINCH signal has been received diff --git a/src/tty/windows.rs b/src/tty/windows.rs index e0e62d04e38c97afa50ef8fecfb7b779f566e7a2..5f83d34b9f7a33dd7c175f0a467fd85fdd1acff7 100644 --- a/src/tty/windows.rs +++ b/src/tty/windows.rs @@ -75,6 +75,7 @@ fn is_a_tty(fd: winapi::DWORD) -> bool { } } +/// Enable raw mode for the TERM pub fn enable_raw_mode() -> Result<Mode> { let handle = try!(get_std_handle(STDIN_FILENO)); let original_mode = try!(get_console_mode(handle)); @@ -91,6 +92,7 @@ pub fn enable_raw_mode() -> Result<Mode> { Ok(original_mode) } +/// Disable Raw mode for the term pub fn disable_raw_mode(original_mode: Mode) -> Result<()> { let handle = try!(get_std_handle(STDIN_FILENO)); check!(kernel32::SetConsoleMode(handle, original_mode)); @@ -223,20 +225,20 @@ static SIGWINCH: atomic::AtomicBool = atomic::ATOMIC_BOOL_INIT; pub type Terminal = Console; -#[derive(Debug)] +#[derive(Clone,Debug)] pub struct Console { stdin_isatty: bool, stdout_handle: winapi::HANDLE, } impl Console { - pub fn new() -> Console { + pub fn new() -> Result<Console> { use std::ptr; let stdout_handle = get_std_handle(STDOUT_FILENO).unwrap_or(ptr::null_mut()); - Console { + Ok(Console { stdin_isatty: is_a_tty(STDIN_FILENO), stdout_handle: stdout_handle, - } + }) } /// Checking for an unsupported TERM in windows is a no-op @@ -252,20 +254,6 @@ impl Console { // See ReadConsoleInputW && WINDOW_BUFFER_SIZE_EVENT // } - pub fn load_capabilities(&mut self) -> Result<()> { - Ok(()) - } - - /// Enable raw mode for the TERM - pub fn enable_raw_mode(&mut self) -> Result<Mode> { - enable_raw_mode() - } - - /// Disable Raw mode for the term - pub fn disable_raw_mode(&mut self, mode: Mode) -> Result<()> { - disable_raw_mode(mode) - } - /// Try to get the number of columns in the current terminal, /// or assume 80 if it fails. pub fn get_columns(&self) -> usize { @@ -301,13 +289,16 @@ impl Console { Ok(()) } - pub fn fill_console_output_character(&mut self, length: winapi::DWORD, pos: winapi::COORD) -> Result<()> { + pub fn fill_console_output_character(&mut self, + length: winapi::DWORD, + pos: winapi::COORD) + -> Result<()> { let mut _count = 0; check!(kernel32::FillConsoleOutputCharacterA(self.stdout_handle, - ' ' as winapi::CHAR, - length, - pos, - &mut _count)); + ' ' as winapi::CHAR, + length, + pos, + &mut _count)); Ok(()) } }