Skip to content
Snippets Groups Projects
lib.rs 47 KiB
Newer Older
gwenn's avatar
gwenn committed
//! Readline for Rust
Main's avatar
Main committed
//!
gwenn's avatar
gwenn committed
//! This implementation is based on [Antirez's Linenoise](https://github.com/antirez/linenoise)
Main's avatar
Main committed
//!
gwenn's avatar
gwenn committed
//! # Example
Main's avatar
Main committed
//!
gwenn's avatar
gwenn committed
//! Usage
Main's avatar
Main committed
//!
gwenn's avatar
gwenn committed
//! ```
//! let mut rl = rustyline::Editor::new();
//! let readline = rl.readline(">> ");
//! match readline {
Main's avatar
Main committed
//!     Ok(line) => println!("Line: {:?}",line),
//!     Err(_)   => println!("No input"),
//! }
gwenn's avatar
gwenn committed
//! ```
#![feature(io)]
#![feature(unicode)]
gwenn's avatar
gwenn committed

extern crate libc;
gwenn's avatar
gwenn committed
#[cfg(unix)]
extern crate unicode_width;
#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
extern crate kernel32;
#[allow(non_camel_case_types)]
mod consts;
pub mod error;
pub mod history;
gwenn's avatar
gwenn committed
mod kill_ring;
gwenn's avatar
gwenn committed
pub mod line_buffer;
use std::fmt;
gwenn's avatar
gwenn committed
use std::io::{self, Read, Write};
gwenn's avatar
gwenn committed
use std::mem;
use std::result;
#[cfg(unix)]
gwenn's avatar
gwenn committed
use std::sync;
use std::sync::atomic;
gwenn's avatar
gwenn committed
#[cfg(unix)]
gwenn's avatar
gwenn committed
use nix::sys::signal;
gwenn's avatar
gwenn committed
#[cfg(unix)]
use nix::sys::termios;
use completion::Completer;
use consts::{KeyPress, char_to_key_press};
use history::History;
gwenn's avatar
gwenn committed
use line_buffer::{LineBuffer, MAX_LINE, WordAction};
gwenn's avatar
gwenn committed
use kill_ring::KillRing;
/// 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
gwenn's avatar
gwenn committed
    prompt_size: Position, // Prompt Unicode width and height
gwenn's avatar
gwenn committed
    line: LineBuffer, // Edited line buffer
gwenn's avatar
gwenn committed
    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
gwenn's avatar
gwenn committed
    snapshot: LineBuffer, // Current edited line before history browsing/completion
    output_handle: Handle, // output handle (for windows)
gwenn's avatar
gwenn committed
#[derive(Copy, Clone, Debug, Default)]
struct Position {
    col: usize,
    row: usize,
impl<'out, 'prompt> State<'out, 'prompt> {
    fn new(out: &'out mut Write,
           output_handle: Handle,
           prompt: &'prompt str,
           history_index: usize)
           -> State<'out, 'prompt> {
        let capacity = MAX_LINE;
        let cols = get_columns(output_handle);
        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),
            output_handle: output_handle,
        }
gwenn's avatar
gwenn committed
    fn snapshot(&mut self) {
gwenn's avatar
gwenn committed
        mem::swap(&mut self.line, &mut self.snapshot);
gwenn's avatar
gwenn committed
    }
gwenn's avatar
gwenn committed
    fn backup(&mut self) {
gwenn's avatar
gwenn committed
        self.snapshot.backup(&self.line);
    /// 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<()> {
gwenn's avatar
gwenn committed
        let prompt_size = self.prompt_size;
        self.refresh(self.prompt, prompt_size)
    }

    fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> {
gwenn's avatar
gwenn committed
        let prompt_size = calculate_position(prompt, Default::default(), self.cols);
        self.refresh(prompt, prompt_size)
gwenn's avatar
gwenn committed
    #[cfg(unix)]
gwenn's avatar
gwenn committed
    fn refresh(&mut self, prompt: &str, prompt_size: Position) -> Result<()> {
        use std::fmt::Write;

        // calculate the position of the end of the input line
gwenn's avatar
gwenn committed
        let end_pos = calculate_position(&self.line, prompt_size, self.cols);
        // calculate the desired position of the cursor
gwenn's avatar
gwenn committed
        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
gwenn's avatar
gwenn committed
        if cursor_row_movement > 0 {
            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");

gwenn's avatar
gwenn committed
        // display the prompt
        ab.push_str(prompt);
gwenn's avatar
gwenn committed
        // display the input line
gwenn's avatar
gwenn committed
        ab.push_str(&self.line);
gwenn's avatar
gwenn committed
        // we have to generate our own newline on line wrap
        if end_pos.col == 0 && end_pos.row > 0 {
            ab.push_str("\n");
gwenn's avatar
gwenn committed
        // position the cursor
        let cursor_row_movement = end_pos.row - cursor.row;
        // move the cursor up as required
        if cursor_row_movement > 0 {
gwenn's avatar
gwenn committed
            write!(ab, "\x1b[{}A", cursor_row_movement).unwrap();
gwenn's avatar
gwenn committed
        // position the cursor within the line
        if cursor.col > 0 {
gwenn's avatar
gwenn committed
            write!(ab, "\r\x1b[{}C", cursor.col).unwrap();
        } else {
            ab.push('\r');
        }

gwenn's avatar
gwenn committed
        self.cursor = cursor;
        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<()> {
        let handle = self.output_handle;
gwenn's avatar
gwenn committed
        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);
gwenn's avatar
gwenn committed
        // 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));
gwenn's avatar
gwenn committed
        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));

        self.cursor = cursor;
gwenn's avatar
gwenn committed
        Ok(())
    }

    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")
gwenn's avatar
gwenn committed
            .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)
gwenn's avatar
gwenn committed
            .field("history_index", &self.history_index)
            .field("snapshot", &self.snapshot)
            .finish()
Main's avatar
Main committed
/// Unsupported Terminals that don't support RAW mode
gwenn's avatar
gwenn committed
static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb", "cons25", "emacs"];
gwenn's avatar
gwenn committed
/// Check to see if `fd` is a TTY
#[cfg(unix)]
gwenn's avatar
gwenn committed
fn is_a_tty(fd: libc::c_int) -> bool {
    unsafe { libc::isatty(fd) != 0 }
#[cfg(windows)]
gwenn's avatar
gwenn committed
fn is_a_tty(fd: winapi::DWORD) -> bool {
    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,
    }
Main's avatar
Main committed
/// Check to see if the current `TERM` is unsupported
fn is_unsupported_term() -> bool {
    use std::ascii::AsciiExt;
    match std::env::var("TERM") {
            let mut unsupported = false;
            for iter in &UNSUPPORTED_TERM {
                unsupported = (*iter).eq_ignore_ascii_case(&term)
gwenn's avatar
gwenn committed
        Err(_) => false,
#[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;
#[cfg(windows)]
gwenn's avatar
gwenn committed
type Mode = winapi::DWORD;
#[cfg(windows)]
gwenn's avatar
gwenn committed
const STDIN_FILENO: winapi::DWORD = winapi::STD_INPUT_HANDLE;
#[cfg(windows)]
gwenn's avatar
gwenn committed
const STDOUT_FILENO: winapi::DWORD = winapi::STD_OUTPUT_HANDLE;
gwenn's avatar
gwenn committed
#[cfg(windows)]
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)
}
Main's avatar
Main committed
/// Enable raw mode for the TERM
gwenn's avatar
gwenn committed
#[cfg(unix)]
fn enable_raw_mode() -> Result<Mode> {
    use nix::errno::Errno::ENOTTY;
gwenn's avatar
gwenn committed
    use nix::sys::termios::{BRKINT, CS8, ECHO, ICANON, ICRNL, IEXTEN, INPCK, ISIG, ISTRIP, IXON,
gwenn's avatar
gwenn committed
                            /* OPOST, */ VMIN, VTIME};
    if !is_a_tty(STDIN_FILENO) {
        try!(Err(nix::Error::from_errno(ENOTTY)));
    let original_term = try!(termios::tcgetattr(STDIN_FILENO));
gwenn's avatar
gwenn committed
    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
gwenn's avatar
gwenn committed
    // we don't want raw output, it turns newlines into straight linefeeds
    //raw.c_oflag = raw.c_oflag & !(OPOST); // disable all output processing
gwenn's avatar
gwenn committed
    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));
gwenn's avatar
gwenn committed
    Ok(original_term)
#[cfg(windows)]
fn enable_raw_mode() -> Result<Mode> {
    let handle = try!(get_std_handle(STDIN_FILENO));
gwenn's avatar
gwenn committed
    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);
gwenn's avatar
gwenn committed
    check!(kernel32::SetConsoleMode(handle, raw));
gwenn's avatar
gwenn committed
    Ok(original_mode)
}
#[cfg(windows)]
gwenn's avatar
gwenn committed
fn get_console_mode(handle: winapi::HANDLE) -> Result<Mode> {
gwenn's avatar
gwenn committed
    let mut original_mode = 0;
gwenn's avatar
gwenn committed
    check!(kernel32::GetConsoleMode(handle, &mut original_mode));
gwenn's avatar
gwenn committed
    Ok(original_mode)
Main's avatar
Main committed
/// Disable Raw mode for the term
gwenn's avatar
gwenn committed
#[cfg(unix)]
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));
gwenn's avatar
gwenn committed
    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"))]
fn get_columns(_: Handle) -> usize {

    unsafe {
        #[repr(C)]
        struct winsize {
            ws_row: c_ushort,
            ws_col: c_ushort,
            ws_xpixel: c_ushort,
gwenn's avatar
gwenn committed
            ws_ypixel: 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,
#[cfg(windows)]
fn get_columns(handle: Handle) -> usize {
gwenn's avatar
gwenn committed
    let mut info = unsafe { mem::zeroed() };
    match unsafe { kernel32::GetConsoleScreenBufferInfo(handle, &mut info) } {
        0 => 80,
gwenn's avatar
gwenn committed
        _ => info.dwSize.X as usize,
gwenn's avatar
gwenn committed
    }
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
gwenn's avatar
gwenn committed
#[cfg(unix)]
fn clear_screen(s: &mut State) -> Result<()> {
    write_and_flush(s.out, b"\x1b[H\x1b[2J")
#[cfg(windows)]
fn clear_screen(s: &mut State) -> Result<()> {
    let handle = s.output_handle;
gwenn's avatar
gwenn committed
    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,
gwenn's avatar
gwenn committed
                                                 ' ' as winapi::CHAR,
                                                 (info.dwSize.X * info.dwSize.Y) as winapi::DWORD,
gwenn's avatar
gwenn committed
                                                 coord,
                                                 &mut _count));
    Ok(())

/// Beep, used for completion when there is nothing to complete or when all
/// the choices were already shown.
fn beep() -> Result<()> {
    write_and_flush(&mut io::stderr(), b"\x07") // TODO bell-style
gwenn's avatar
gwenn committed
/// 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))]
gwenn's avatar
gwenn committed
fn calculate_position(s: &str, orig: Position, cols: usize) -> Position {
gwenn's avatar
gwenn committed
    let mut esc_seq = 0;
    for c in s.chars() {
        let cw = if esc_seq == 1 {
            if c == '[' {
                // CSI
                esc_seq = 2;
gwenn's avatar
gwenn committed
                // 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;
gwenn's avatar
gwenn committed
                // 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;
gwenn's avatar
gwenn committed
    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<()> {
gwenn's avatar
gwenn committed
    if let Some(push) = s.line.insert(ch) {
        if push {
gwenn's avatar
gwenn committed
            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;
gwenn's avatar
gwenn committed
                let bits = ch.encode_utf8();
                let bits = bits.as_slice();
                write_and_flush(s.out, bits)
                s.refresh_line()
            s.refresh_line()
gwenn's avatar
gwenn committed
// Yank/paste `text` at current position.
gwenn's avatar
gwenn committed
fn edit_yank(s: &mut State, text: &str) -> Result<()> {
gwenn's avatar
gwenn committed
    if let Some(_) = s.line.yank(text) {
        s.refresh_line()
gwenn's avatar
gwenn committed
        Ok(())
gwenn's avatar
gwenn committed
    }
}

gwenn's avatar
gwenn committed
// Delete previously yanked text and yank/paste `text` at current position.
gwenn's avatar
gwenn committed
fn edit_yank_pop(s: &mut State, yank_size: usize, text: &str) -> Result<()> {
gwenn's avatar
gwenn committed
    s.line.yank_pop(yank_size, text);
gwenn's avatar
gwenn committed
    edit_yank(s, text)
}

/// Move cursor on the left.
fn edit_move_left(s: &mut State) -> Result<()> {
gwenn's avatar
gwenn committed
    if s.line.move_left() {
        s.refresh_line()
    } else {
        Ok(())
    }
}

/// Move cursor on the right.
fn edit_move_right(s: &mut State) -> Result<()> {
gwenn's avatar
gwenn committed
    if s.line.move_right() {
        s.refresh_line()
    } else {
        Ok(())
    }
}

/// Move cursor to the start of the line.
fn edit_move_home(s: &mut State) -> Result<()> {
gwenn's avatar
gwenn committed
    if s.line.move_home() {
        s.refresh_line()
    } else {
        Ok(())
    }
}

/// Move cursor to the end of the line.
fn edit_move_end(s: &mut State) -> Result<()> {
gwenn's avatar
gwenn committed
    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.
fn edit_delete(s: &mut State) -> Result<()> {
gwenn's avatar
gwenn committed
    if s.line.delete() {
        s.refresh_line()
    } else {
        Ok(())
    }
}

/// Backspace implementation.
fn edit_backspace(s: &mut State) -> Result<()> {
gwenn's avatar
gwenn committed
    if s.line.backspace() {
        s.refresh_line()
    } else {
        Ok(())
    }
}

/// Kill the text from point to the end of the line.
gwenn's avatar
gwenn committed
fn edit_kill_line(s: &mut State) -> Result<Option<String>> {
gwenn's avatar
gwenn committed
    if let Some(text) = s.line.kill_line() {
gwenn's avatar
gwenn committed
        try!(s.refresh_line());
        Ok(Some(text))
gwenn's avatar
gwenn committed
        Ok(None)
    }
}

/// Kill backward from point to the beginning of the line.
gwenn's avatar
gwenn committed
fn edit_discard_line(s: &mut State) -> Result<Option<String>> {
gwenn's avatar
gwenn committed
    if let Some(text) = s.line.discard_line() {
gwenn's avatar
gwenn committed
        try!(s.refresh_line());
        Ok(Some(text))
gwenn's avatar
gwenn committed
        Ok(None)
    }
}

/// Exchange the char before cursor with the character at cursor.
fn edit_transpose_chars(s: &mut State) -> Result<()> {
gwenn's avatar
gwenn committed
    if s.line.transpose_chars() {
        s.refresh_line()
gwenn's avatar
gwenn committed
fn edit_move_to_prev_word(s: &mut State) -> Result<()> {
gwenn's avatar
gwenn committed
    if s.line.move_to_prev_word() {
gwenn's avatar
gwenn committed
        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
{
gwenn's avatar
gwenn committed
    if let Some(text) = s.line.delete_prev_word(test) {
gwenn's avatar
gwenn committed
        try!(s.refresh_line());
        Ok(Some(text))
gwenn's avatar
gwenn committed
        Ok(None)
gwenn's avatar
gwenn committed
fn edit_move_to_next_word(s: &mut State) -> Result<()> {
gwenn's avatar
gwenn committed
    if s.line.move_to_next_word() {
gwenn's avatar
gwenn committed
        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>> {
gwenn's avatar
gwenn committed
    if let Some(text) = s.line.delete_word() {
gwenn's avatar
gwenn committed
        try!(s.refresh_line());
        Ok(Some(text))
    } else {
        Ok(None)
    }
}

gwenn's avatar
gwenn committed
fn edit_word(s: &mut State, a: WordAction) -> Result<()> {
    if s.line.edit_word(a) {
gwenn's avatar
gwenn committed
        s.refresh_line()
    } else {
        Ok(())
    }
}

gwenn's avatar
gwenn committed
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<()> {
gwenn's avatar
gwenn committed
    if history.is_empty() {
        return Ok(());
    }
    if s.history_index == history.len() {
        if prev {
gwenn's avatar
gwenn committed
            // Save the current edited line before to overwrite it
gwenn's avatar
gwenn committed
            s.snapshot();
        } else {
gwenn's avatar
gwenn committed
            return Ok(());
gwenn's avatar
gwenn committed
    } else if s.history_index == 0 && prev {
        return Ok(());
    }
    if prev {
        s.history_index -= 1;
    } else {
gwenn's avatar
gwenn committed
        s.history_index += 1;
gwenn's avatar
gwenn committed
    if s.history_index < history.len() {
        let buf = history.get(s.history_index).unwrap();
gwenn's avatar
gwenn committed
        s.line.update(buf, buf.len());
gwenn's avatar
gwenn committed
    } else {
        // Restore current edited line
        s.snapshot();
gwenn's avatar
gwenn committed
    };
    s.refresh_line()
/// Completes the line/word
gwenn's avatar
gwenn committed
fn complete_line<R: io::Read>(chars: &mut io::Chars<R>,
                              s: &mut State,
                              completer: &Completer)
                              -> Result<Option<char>> {
gwenn's avatar
gwenn committed
    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]);
                try!(s.refresh_line());
                // Restore current edited line
                s.snapshot();
                try!(s.refresh_line());
                s.snapshot();
            }

            ch = try!(chars.next().unwrap());
            let key = char_to_key_press(ch);
            match key {
                KeyPress::TAB => {
gwenn's avatar
gwenn committed
                    i = (i + 1) % (candidates.len() + 1); // Circular
                    if i == candidates.len() {
                        try!(beep());
                    }
gwenn's avatar
gwenn committed
                }
                KeyPress::ESC => {
                    // Re-show original buffer
                    s.snapshot();
                    if i < candidates.len() {
                        try!(s.refresh_line());
gwenn's avatar
gwenn committed
                    return Ok(None);
                }
                _ => {
                    break;
/// Incremental search
gwenn's avatar
gwenn committed
#[cfg_attr(feature="clippy", allow(if_not_else))]
gwenn's avatar
gwenn committed
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
gwenn's avatar
gwenn committed
    s.snapshot();

    let mut search_buf = String::new();
    let mut history_idx = history.len() - 1;
gwenn's avatar
gwenn committed
    let mut reverse = true;
    let mut success = true;

    let mut ch;
    let mut key;
    // Display the reverse-i-search prompt and process chars
    loop {
gwenn's avatar
gwenn committed
        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();
gwenn's avatar
gwenn committed
                    continue;
                }
                KeyPress::CTRL_R => {
gwenn's avatar
gwenn committed
                    reverse = true;
                    if history_idx > 0 {
                        history_idx -= 1;
                    } else {
                        success = false;
                        continue;
                    }
gwenn's avatar
gwenn committed
                }
gwenn's avatar
gwenn committed
                KeyPress::CTRL_S => {
                    reverse = false;
                    if history_idx < history.len() - 1 {
                        history_idx += 1;
                    } else {
                        success = false;
                        continue;
                    }
                }
                KeyPress::CTRL_G => {
gwenn's avatar
gwenn committed
                    // Restore current edited line (before search)
                    s.snapshot();
                    try!(s.refresh_line());
gwenn's avatar
gwenn committed
                    return Ok(None);
                }
                _ => break,
gwenn's avatar
gwenn committed
        success = match history.search(&search_buf, history_idx, reverse) {
            Some(idx) => {
                history_idx = idx;
                let entry = history.get(idx).unwrap();
gwenn's avatar
gwenn committed
                let pos = entry.find(&search_buf).unwrap();
gwenn's avatar
gwenn committed
                s.line.update(entry, pos);
gwenn's avatar
gwenn committed
            _ => false,
gwenn's avatar
gwenn committed
#[cfg(unix)]
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());
gwenn's avatar
gwenn committed
    if seq1 == '[' {
        // ESC [ sequences.
        let seq2 = try!(chars.next().unwrap());
gwenn's avatar
gwenn committed
        if seq2.is_digit(10) {
            // Extended escape, read additional byte.
            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
gwenn's avatar
gwenn committed
                _ => Ok(KeyPress::UNKNOWN_ESC_SEQ),
gwenn's avatar
gwenn committed
    } else if seq1 == 'O' {
        // ESC O sequences.
        let seq2 = try!(chars.next().unwrap());
        match seq2 {
            'F' => Ok(KeyPress::CTRL_E),
            'H' => Ok(KeyPress::CTRL_A),
gwenn's avatar
gwenn committed
            _ => Ok(KeyPress::UNKNOWN_ESC_SEQ),
        }
    } 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
gwenn's avatar
gwenn committed
        match seq1 {
gwenn's avatar
gwenn committed
            'b' | 'B' => Ok(KeyPress::ESC_B),
            'c' | 'C' => Ok(KeyPress::ESC_C),
gwenn's avatar
gwenn committed
            'd' | 'D' => Ok(KeyPress::ESC_D),
gwenn's avatar
gwenn committed
            'f' | 'F' => Ok(KeyPress::ESC_F),
            'l' | 'L' => Ok(KeyPress::ESC_L),
gwenn's avatar
gwenn committed
            't' | 'T' => Ok(KeyPress::ESC_T),
gwenn's avatar
gwenn committed
            'u' | 'U' => Ok(KeyPress::ESC_U),
gwenn's avatar
gwenn committed
            'y' | 'Y' => Ok(KeyPress::ESC_Y),
gwenn's avatar
gwenn committed
            '\x08' | '\x7f' => Ok(KeyPress::ESC_BACKSPACE),
gwenn's avatar
gwenn committed
            _ => {
                // writeln!(io::stderr(), "key: {:?}, seq1: {:?}", KeyPress::ESC, seq1).unwrap();
gwenn's avatar
gwenn committed
                Ok(KeyPress::UNKNOWN_ESC_SEQ)
            }
        }
#[cfg(windows)]
fn escape_sequence<R: io::Read>(chars: &mut io::Chars<R>) -> Result<KeyPress> {
    Ok(KeyPress::UNKNOWN_ESC_SEQ)
gwenn's avatar
gwenn committed
#[cfg(unix)]
fn stdin() -> Result<io::Stdin> {
    Ok(io::stdin())
}
#[cfg(windows)]
gwenn's avatar
gwenn committed
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)
}

gwenn's avatar
gwenn committed
#[cfg(windows)]
struct InputBuffer(winapi::HANDLE);
#[cfg(windows)]
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;
        let mut esc_seen = false;
        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);
                }
            }
            unimplemented!()
        }
Main's avatar
Main committed
/// Handles reading and editting the readline buffer.
/// It will also handle special inputs in an appropriate fashion
/// (e.g., C-c will exit readline)
gwenn's avatar
gwenn committed
#[cfg_attr(feature="clippy", allow(cyclomatic_complexity))]
gwenn's avatar
gwenn committed
fn readline_edit(prompt: &str,
                 history: &mut History,
gwenn's avatar
gwenn committed
                 completer: Option<&Completer>,
gwenn's avatar
gwenn committed
                 kill_ring: &mut KillRing,
                 original_mode: Mode)
gwenn's avatar
gwenn committed
                 -> Result<String> {
    let mut stdout = io::stdout();
    let stdout_handle = try!(stdout_handle());
gwenn's avatar
gwenn committed
    kill_ring.reset();
    let mut s = State::new(&mut stdout, stdout_handle, prompt, history.len());
    try!(s.refresh_line());
gwenn's avatar
gwenn committed
    let stdin = try!(stdin());
    let mut chars = stdin.chars(); // TODO stdin.lock() ???
gwenn's avatar
gwenn committed
        let c = chars.next().unwrap();