Newer
Older
//! This implementation is based on [Antirez's Linenoise](https://github.com/antirez/linenoise)
//! ```
//! let mut rl = rustyline::Editor::new();
//! let readline = rl.readline(">> ");
//! match readline {
//! Ok(line) => println!("Line: {:?}",line),
//! Err(_) => println!("No input"),
//! }
#![feature(io)]
#![feature(unicode)]
extern crate nix;
extern crate unicode_width;
#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
extern crate kernel32;
pub mod completion;
use std::path::Path;
use completion::Completer;
use consts::{KeyPress, char_to_key_press};
/// The error type for I/O and Linux Syscalls (Errno)
pub type Result<T> = result::Result<T, error::ReadlineError>;
#[cfg(unix)]
type Handle = ();
#[cfg(windows)]
type Handle = winapi::HANDLE;
#[cfg(windows)]
macro_rules! check {
($funcall:expr) => (
if unsafe { $funcall } == 0 {
try!(Err(io::Error::last_os_error()));
}
);
}
// Represent the state during line editing.
struct State<'out, 'prompt> {
out: &'out mut Write,
prompt: &'prompt str, // Prompt to display
prompt_size: Position, // Prompt Unicode width and height
cursor: Position, // Cursor position (relative to the start of the prompt for `row`)
cols: usize, // Number of columns in terminal
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
output_handle: Handle, // output handle (for windows)
#[derive(Copy, Clone, Debug, Default)]
struct Position {
col: usize,
row: usize,
prompt: &'prompt str,
history_index: usize)
let prompt_size = calculate_position(prompt, Default::default(), cols);
out: out,
prompt: prompt,
prompt_size: prompt_size,
line: LineBuffer::with_capacity(capacity),
cursor: prompt_size,
cols: cols,
old_rows: prompt_size.row,
history_index: history_index,
snapshot: LineBuffer::with_capacity(capacity),
/// Rewrite the currently edited line accordingly to the buffer content,
/// cursor position, and number of columns of the terminal.
fn refresh_line(&mut self) -> Result<()> {
let prompt_size = self.prompt_size;
self.refresh(self.prompt, prompt_size)
}
fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> {
let prompt_size = calculate_position(prompt, Default::default(), self.cols);
self.refresh(prompt, prompt_size)
fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> {
// calculate the position of the end of the input line
let end_pos = calculate_position(&self.line, prompt_size, self.cols);
// calculate the desired position of the cursor
let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols);
let mut ab = String::new();
let cursor_row_movement = self.old_rows - self.cursor.row;
// move the cursor down as required
write!(ab, "\x1b[{}B", cursor_row_movement).unwrap();
}
// clear old rows
for _ in 0..self.old_rows {
ab.push_str("\r\x1b[0K\x1b[1A");
// clear the line
ab.push_str("\r\x1b[0K");
// we have to generate our own newline on line wrap
if end_pos.col == 0 && end_pos.row > 0 {
ab.push_str("\n");
// position the cursor
let cursor_row_movement = end_pos.row - cursor.row;
// move the cursor up as required
if cursor_row_movement > 0 {
write!(ab, "\x1b[{}A", cursor_row_movement).unwrap();
// position the cursor within the line
if cursor.col > 0 {
self.old_rows = end_pos.row;
write_and_flush(self.out, ab.as_bytes())
}
#[cfg(windows)]
fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> {
if cfg!(test) && handle.is_null() {
return Ok(());
}
// calculate the position of the end of the input line
let end_pos = calculate_position(&self.line, prompt_size, self.cols);
// calculate the desired position of the cursor
let cursor = calculate_position(&self.line[..self.line.pos()], prompt_size, self.cols);
// position at the start of the prompt, clear to end of previous input
let mut info = unsafe { mem::zeroed() };
check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info));
info.dwCursorPosition.X = 0;
info.dwCursorPosition.Y -= self.cursor.row as i16;
check!(kernel32::SetConsoleCursorPosition(handle, info.dwCursorPosition));
let mut _count = 0;
check!(kernel32::FillConsoleOutputCharacterA(handle,
' ' as winapi::CHAR,
(info.dwSize.X * info.dwSize.Y) as winapi::DWORD, // FIXME
info.dwCursorPosition,
&mut _count));
let mut ab = String::new();
// display the prompt
ab.push_str(prompt);
// display the input line
ab.push_str(&self.line);
try!(write_and_flush(self.out, ab.as_bytes()));
// position the cursor
check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info));
info.dwCursorPosition.X = cursor.col as i16;
info.dwCursorPosition.Y -= (end_pos.row - cursor.row) as i16;
check!(kernel32::SetConsoleCursorPosition(handle, info.dwCursorPosition));
}
fn update_columns(&mut self) {
self.cols = get_columns(self.output_handle);
}
impl<'out, 'prompt> fmt::Debug for State<'out, 'prompt> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("State")
.field("prompt", &self.prompt)
.field("prompt_size", &self.prompt_size)
.field("buf", &self.line)
.field("cursor", &self.cursor)
.field("cols", &self.cols)
.field("old_rows", &self.old_rows)
.field("history_index", &self.history_index)
.field("snapshot", &self.snapshot)
.finish()
/// Unsupported Terminals that don't support RAW mode
static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"];
unsafe { libc::isatty(fd) != 0 }
let handle = get_std_handle(fd);
match handle {
Ok(handle) => {
// If this function doesn't fail then fd is a TTY
get_console_mode(handle).is_ok()
}
Err(_) => false,
}
/// Check to see if the current `TERM` is unsupported
use std::ascii::AsciiExt;
let mut unsupported = false;
for iter in &UNSUPPORTED_TERM {
unsupported = (*iter).eq_ignore_ascii_case(&term)
#[cfg(unix)]
type Mode = termios::Termios;
#[cfg(unix)]
const STDIN_FILENO: libc::c_int = libc::STDIN_FILENO;
#[cfg(unix)]
const STDOUT_FILENO: libc::c_int = libc::STDOUT_FILENO;
const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE;
const STDOUT_FILENO: winapi::DWORD = winapi::STD_OUTPUT_HANDLE;
fn get_std_handle(fd: winapi::DWORD) -> Result<winapi::HANDLE> {
let handle = unsafe { kernel32::GetStdHandle(fd) };
if handle == winapi::INVALID_HANDLE_VALUE {
try!(Err(io::Error::last_os_error()));
} else if handle.is_null() {
try!(Err(io::Error::new(io::ErrorKind::Other,
"no stdio handle available for this process")));
}
Ok(handle)
}
fn enable_raw_mode() -> Result<Mode> {
use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON,
try!(Err(nix::Error::from_errno(ENOTTY)));
let original_term = try!(termios::tcgetattr(STDIN_FILENO));
let mut raw = original_term;
raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON); // disable BREAK interrupt, CR to NL conversion on input, input parity check, strip high bit (bit 8), output flow control
// we don't want raw output, it turns newlines into straight linefeeds
//raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing
raw.c_cflag = raw.c_cflag | (CS8); // character-size mark (8 bits)
raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG); // disable echoing, canonical mode, extended input processing and signals
raw.c_cc[VMIN] = 1; // One character-at-a-time input
raw.c_cc[VTIME] = 0; // with blocking read
try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &raw));
#[cfg(windows)]
fn enable_raw_mode() -> Result<Mode> {
let handle = try!(get_std_handle(STDIN_FILENO));
let original_mode = try!(get_console_mode(handle));
let raw = original_mode &
!(winapi::wincon::ENABLE_LINE_INPUT | winapi::wincon::ENABLE_ECHO_INPUT |
winapi::wincon::ENABLE_PROCESSED_INPUT);
fn get_console_mode(handle: winapi::HANDLE) -> Result<Mode> {
check!(kernel32::GetConsoleMode(handle, &mut original_mode));
fn disable_raw_mode(original_mode: Mode) -> Result<()> {
try!(termios::tcsetattr(STDIN_FILENO, termios::TCSAFLUSH, &original_mode));
#[cfg(windows)]
fn disable_raw_mode(original_mode: Mode) -> Result<()> {
let handle = try!(get_std_handle(STDIN_FILENO));
check!(kernel32::SetConsoleMode(handle, original_mode));
Ok(())
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
const TIOCGWINSZ: libc::c_ulong = 0x40087468;
#[cfg(any(target_os = "linux", target_os = "android"))]
const TIOCGWINSZ: libc::c_ulong = 0x5413;
/// Try to get the number of columns in the current terminal,
/// or assume 80 if it fails.
#[cfg(any(target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "freebsd"))]
use libc::c_ushort;
unsafe {
#[repr(C)]
struct winsize {
ws_row: c_ushort,
ws_col: c_ushort,
ws_xpixel: c_ushort,
let mut size: winsize = mem::zeroed();
match libc::ioctl(STDOUT_FILENO, TIOCGWINSZ, &mut size) {
0 => size.ws_col as usize, // TODO getCursorPosition
_ => 80,
fn get_columns(handle: Handle) -> usize {
let mut info = unsafe { mem::zeroed() };
match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } {
0 => 80,
fn write_and_flush(w: &mut Write, buf: &[u8]) -> Result<()> {
try!(w.write_all(buf));
try!(w.flush());
/// Clear the screen. Used to handle ctrl+l
fn clear_screen(s: &mut State) -> Result<()> {
write_and_flush(s.out, b"\x1b[H\x1b[2J")
fn clear_screen(s: &mut State) -> Result<()> {
let handle = s.output_handle;
let mut info = unsafe { mem::zeroed() };
check!(kernel32::GetConsoleScreenBufferInfo(handle, &mut info));
let coord = winapi::COORD { X: 0, Y: 0 };
check!(kernel32::SetConsoleCursorPosition(handle, coord));
let mut _count = 0;
check!(kernel32::FillConsoleOutputCharacterA(handle,
' ' as winapi::CHAR,
(info.dwSize.X * info.dwSize.Y) as winapi::DWORD,
/// Beep, used for completion when there is nothing to complete or when all
/// the choices were already shown.
write_and_flush(&mut io::stderr(), b"\x07") // TODO bell-style
/// Calculate the number of columns and rows used to display `s` on a `cols` width terminal
/// starting at `orig`.
/// Control characters are treated as having zero width.
/// Characters with 2 column width are correctly handled (not splitted).
#[cfg_attr(feature="clippy", allow(if_same_then_else))]
fn calculate_position(s: &str, orig: Position, cols: usize) -> Position {
let mut pos = orig;
let mut esc_seq = 0;
for c in s.chars() {
let cw = if esc_seq == 1 {
if c == '[' {
// CSI
esc_seq = 2;
// two-character sequence
esc_seq = 0;
}
None
} else if esc_seq == 2 {
if c == ';' || (c >= '0' && c <= '9') {
} else if c == 'm' {
// last
esc_seq = 0;
// not supported
esc_seq = 0;
}
None
} else if c == '\x1b' {
esc_seq = 1;
None
} else if c == '\n' {
pos.col = 0;
pos.row += 1;
None
} else {
unicode_width::UnicodeWidthChar::width(c)
};
if let Some(cw) = cw {
pos.col += cw;
if pos.col > cols {
pos.row += 1;
pos.col = cw;
if pos.col == cols {
pos.col = 0;
pos.row += 1;
}
pos
/// Insert the character `ch` at cursor current position.
fn edit_insert(s: &mut State, ch: char) -> Result<()> {
if let Some(push) = s.line.insert(ch) {
if push {
if s.cursor.col + unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) < s.cols {
// Avoid a full update of the line in the trivial case.
let cursor = calculate_position(&s.line[..s.line.pos()], s.prompt_size, s.cols);
s.cursor = cursor;
let bits = ch.encode_utf8();
let bits = bits.as_slice();
write_and_flush(s.out, bits)
}
} else {
Ok(())
}
}
if let Some(_) = s.line.yank(text) {
s.refresh_line()
// Delete previously yanked text and yank/paste `text` at current position.
fn edit_yank_pop(s: &mut State, yank_size: usize, text: &str) -> Result<()> {
fn edit_move_left(s: &mut State) -> Result<()> {
} else {
Ok(())
}
}
/// Move cursor on the right.
fn edit_move_right(s: &mut State) -> Result<()> {
s.refresh_line()
} else {
Ok(())
}
}
/// Move cursor to the start of the line.
fn edit_move_home(s: &mut State) -> Result<()> {
} else {
Ok(())
}
}
/// Move cursor to the end of the line.
fn edit_move_end(s: &mut State) -> Result<()> {
if s.line.move_end() {
s.refresh_line()
} else {
Ok(())
}
}
/// Delete the character at the right of the cursor without altering the cursor
/// position. Basically this is what happens with the "Delete" keyboard key.
} else {
Ok(())
}
}
/// Backspace implementation.
fn edit_backspace(s: &mut State) -> Result<()> {
} else {
Ok(())
}
}
/// Kill the text from point to the end of the line.
}
}
/// Kill backward from point to the beginning of the line.
}
}
/// Exchange the char before cursor with the character at cursor.
fn edit_transpose_chars(s: &mut State) -> Result<()> {
} else {
Ok(())
}
}
s.refresh_line()
} else {
Ok(())
}
}
/// Delete the previous word, maintaining the cursor at the start of the
/// current word.
fn edit_delete_prev_word<F>(s: &mut State, test: F) -> Result<Option<String>>
where F: Fn(char) -> bool
{
if let Some(text) = s.line.delete_prev_word(test) {
s.refresh_line()
} else {
Ok(())
}
}
/// Kill from the cursor to the end of the current word, or, if between words, to the end of the next word.
fn edit_delete_word(s: &mut State) -> Result<Option<String>> {
try!(s.refresh_line());
Ok(Some(text))
} else {
Ok(None)
}
}
fn edit_transpose_words(s: &mut State) -> Result<()> {
if s.line.transpose_words() {
s.refresh_line()
} else {
Ok(())
}
}
/// Substitute the currently edited line with the next or previous history
/// entry.
fn edit_history_next(s: &mut State, history: &History, prev: bool) -> Result<()> {
if history.is_empty() {
return Ok(());
}
if s.history_index == history.len() {
} else if s.history_index == 0 && prev {
return Ok(());
}
if prev {
s.history_index -= 1;
if s.history_index < history.len() {
let buf = history.get(s.history_index).unwrap();
} else {
// Restore current edited line
s.snapshot();
fn complete_line<R: io::Read>(chars: &mut io::Chars<R>,
s: &mut State,
completer: &Completer)
-> Result<Option<char>> {
let (start, candidates) = try!(completer.complete(&s.line, s.line.pos()));
if candidates.is_empty() {
try!(beep());
Ok(None)
} else {
// Save the current edited line before to overwrite it
s.backup();
let mut ch;
let mut i = 0;
loop {
// Show completion or original buffer
if i < candidates.len() {
completer.update(&mut s.line, start, &candidates[i]);
// Restore current edited line
s.snapshot();
}
ch = try!(chars.next().unwrap());
let key = char_to_key_press(ch);
match key {
KeyPress::TAB => {
if i == candidates.len() {
try!(beep());
}
}
}
}
Ok(Some(ch))
}
}
fn reverse_incremental_search<R: io::Read>(chars: &mut io::Chars<R>,
s: &mut State,
history: &History)
-> Result<Option<KeyPress>> {
if history.is_empty() {
return Ok(None);
}
// Save the current edited line (and cursor position) before to overwrite it
let mut search_buf = String::new();
let mut history_idx = history.len() - 1;
let mut success = true;
let mut ch;
let mut key;
// Display the reverse-i-search prompt and process chars
loop {
let prompt = if success {
format!("(reverse-i-search)`{}': ", search_buf)
} else {
format!("(failed reverse-i-search)`{}': ", search_buf)
};
try!(s.refresh_prompt_and_line(&prompt));
ch = try!(chars.next().unwrap());
if !ch.is_control() {
search_buf.push(ch);
} else {
key = char_to_key_press(ch);
if key == KeyPress::ESC {
key = try!(escape_sequence(chars));
}
match key {
KeyPress::CTRL_H | KeyPress::BACKSPACE => {
search_buf.pop();
if history_idx > 0 {
history_idx -= 1;
} else {
success = false;
continue;
}
KeyPress::CTRL_S => {
reverse = false;
if history_idx < history.len() - 1 {
history_idx += 1;
} else {
success = false;
continue;
}
}
// Restore current edited line (before search)
s.snapshot();
success = match history.search(&search_buf, history_idx, reverse) {
Some(idx) => {
history_idx = idx;
let entry = history.get(idx).unwrap();
};
}
Ok(Some(key))
}
fn escape_sequence<R: io::Read>(chars: &mut io::Chars<R>) -> Result<KeyPress> {
// Read the next two bytes representing the escape sequence.
let seq1 = try!(chars.next().unwrap());
let seq2 = try!(chars.next().unwrap());
let seq3 = try!(chars.next().unwrap());
if seq3 == '~' {
match seq2 {
'3' => Ok(KeyPress::ESC_SEQ_DELETE),
// TODO '1' // Home
// TODO '4' // End
_ => Ok(KeyPress::UNKNOWN_ESC_SEQ),
}
} else {
Ok(KeyPress::UNKNOWN_ESC_SEQ)
}
} else {
match seq2 {
'A' => Ok(KeyPress::CTRL_P), // Up
'B' => Ok(KeyPress::CTRL_N), // Down
'C' => Ok(KeyPress::CTRL_F), // Right
'D' => Ok(KeyPress::CTRL_B), // Left
'F' => Ok(KeyPress::CTRL_E), // End
'H' => Ok(KeyPress::CTRL_A), // Home
let seq2 = try!(chars.next().unwrap());
match seq2 {
'F' => Ok(KeyPress::CTRL_E),
'H' => Ok(KeyPress::CTRL_A),
}
} else {
// TODO ESC-N (n): search history forward not interactively
// TODO ESC-P (p): search history backward not interactively
// TODO ESC-R (r): Undo all changes made to this line.
// TODO ESC-<: move to first entry in history
// TODO ESC->: move to last entry in history
'b' | 'B' => Ok(KeyPress::ESC_B),
'c' | 'C' => Ok(KeyPress::ESC_C),
'f' | 'F' => Ok(KeyPress::ESC_F),
'l' | 'L' => Ok(KeyPress::ESC_L),
// writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::ESC, seq1).unwrap();
#[cfg(windows)]
fn escape_sequence<R: io::Read>(chars: &mut io::Chars<R>) -> Result<KeyPress> {
#[cfg(unix)]
fn stdin() -> Result<io::Stdin> {
Ok(io::stdin())
}
fn stdin() -> Result<InputBuffer> {
let handle = try!(get_std_handle(STDIN_FILENO));
Ok(InputBuffer(handle))
}
#[cfg(unix)]
fn stdout_handle() -> Result<Handle> {
Ok(())
}
#[cfg(windows)]
fn stdout_handle() -> Result<Handle> {
let handle = try!(get_std_handle(STDOUT_FILENO));
Ok(handle)
}
#[cfg(windows)]
struct InputBuffer(winapi::HANDLE);
impl InputBuffer {
fn single_byte(c: u8, buf: &mut [u8]) -> io::Result<usize> {
buf[0] = c;
Ok(1)
}
}
#[cfg(windows)]
impl Read for InputBuffer {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut rec: winapi::INPUT_RECORD = unsafe { mem::zeroed() };
let mut count = 0;
loop {
check!(kernel32::ReadConsoleInputW(self.0, &mut rec, 1 as winapi::DWORD, &mut count));
// TODO ENABLE_WINDOW_INPUT ???
if rec.EventType == winapi::WINDOW_BUFFER_SIZE_EVENT {
SIGWINCH.store(true, atomic::Ordering::SeqCst);
return Err(io::Error::new(io::ErrorKind::Other, "WINDOW_BUFFER_SIZE_EVENT"));
} else if rec.EventType != winapi::KEY_EVENT {
continue;
}
let key_event = unsafe { rec.KeyEvent() };
if key_event.bKeyDown == 0 &&
key_event.wVirtualKeyCode != winapi::VK_MENU as winapi::WORD {
let ctrl = key_event.dwControlKeyState &
(winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED) ==
(winapi::LEFT_CTRL_PRESSED | winapi::RIGHT_CTRL_PRESSED);
let meta = (key_event.dwControlKeyState &
(winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED) ==
(winapi::LEFT_ALT_PRESSED | winapi::RIGHT_ALT_PRESSED)) ||
esc_seen;
// TODO How to support surrogate pair ?
let utf16 = key_event.UnicodeChar;
if utf16 == 0 {
match key_event.wVirtualKeyCode as i32 {
winapi::VK_LEFT => return InputBuffer::single_byte(b'\x02', buf),
winapi::VK_RIGHT => return InputBuffer::single_byte(b'\x06', buf),
winapi::VK_UP => return InputBuffer::single_byte(b'\x10', buf),
winapi::VK_DOWN => return InputBuffer::single_byte(b'\x0e', buf),
// winapi::VK_DELETE => b"\x1b[3~",
winapi::VK_HOME => return InputBuffer::single_byte(b'\x01', buf),
winapi::VK_END => return InputBuffer::single_byte(b'\x05', buf),
_ => continue,
};
} else if utf16 == 27 {
esc_seen = true;
continue;
} else {
if ctrl {
} else if meta {
} else {
// return utf8.read(buf);
}
/// Handles reading and editting the readline buffer.
/// It will also handle special inputs in an appropriate fashion
/// (e.g., C-c will exit readline)
let mut stdout = io::stdout();
let stdout_handle = try!(stdout_handle());
let mut s = State::new(&mut stdout, stdout_handle, prompt, history.len());
let mut chars = stdin.chars(); // TODO stdin.lock() ???
Main
committed
loop {