Skip to content
Snippets Groups Projects
lib.rs 39.4 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
//! ```
gwenn's avatar
gwenn committed
//! let config = rustyline::Config::default();
//! let mut rl = rustyline::Editor::<()>::new(config);
gwenn's avatar
gwenn committed
//! 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
#![allow(unknown_lints)]
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;
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;
gwenn's avatar
gwenn committed
pub mod config;
gwenn's avatar
gwenn committed
#[macro_use]
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;
gwenn's avatar
gwenn committed
use std::sync::atomic;
gwenn's avatar
gwenn committed
#[cfg(unix)]
gwenn's avatar
gwenn committed
use nix::sys::signal;
use completion::{Completer, longest_common_prefix};
use consts::KeyPress;
use history::History;
gwenn's avatar
gwenn committed
use line_buffer::{LineBuffer, MAX_LINE, WordAction};
gwenn's avatar
gwenn committed
use kill_ring::KillRing;
gwenn's avatar
gwenn committed
pub use config::{CompletionType, Config, HistoryDuplicates};
/// The error type for I/O and Linux Syscalls (Errno)
pub type Result<T> = result::Result<T, error::ReadlineError>;

// 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: tty::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: tty::Handle,
           prompt: &'prompt str,
           history_index: usize)
           -> State<'out, 'prompt> {
        let capacity = MAX_LINE;
        let cols = tty::get_columns(output_handle);
gwenn's avatar
gwenn committed
        let prompt_size = calculate_position(prompt, Position::default(), cols);
        State {
            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, Position::default(), self.cols);
gwenn's avatar
gwenn committed
        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;
        // 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,
gwenn's avatar
gwenn committed
                                                 (info.dwSize.X * (self.old_rows as i16 +1)) as winapi::DWORD,
gwenn's avatar
gwenn committed
                                                 info.dwCursorPosition,
                                                 &mut _count));
        let mut ab = String::new();
        // display the prompt
        ab.push_str(prompt); // TODO handle ansi escape code (SetConsoleTextAttribute)
gwenn's avatar
gwenn committed
        // 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
        self.old_rows = end_pos.row;

gwenn's avatar
gwenn committed
        Ok(())
    }

    fn update_columns(&mut self) {
        self.cols = tty::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()
fn write_and_flush(w: &mut Write, buf: &[u8]) -> Result<()> {
    try!(w.write_all(buf));
    try!(w.flush());
/// 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).
gwenn's avatar
gwenn committed
#[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();
    }
    s.refresh_line()
}

/// Substitute the currently edited line with the first/last history entry.
fn edit_history(s: &mut State, history: &History, first: bool) -> Result<()> {
    if history.is_empty() {
        return Ok(());
    }
    if s.history_index == history.len() {
        if first {
            // Save the current edited line before to overwrite it
            s.snapshot();
        } else {
            return Ok(());
        }
    } else if s.history_index == 0 && first {
        return Ok(());
    }
    if first {
        s.history_index = 0;
        let buf = history.get(s.history_index).unwrap();
        s.line.update(buf, buf.len());
    } else {
        s.history_index = history.len();
        // Restore current edited line
        s.snapshot();
    }
gwenn's avatar
gwenn committed
    s.refresh_line()
/// Completes the line/word
fn complete_line<R: Read>(rdr: &mut tty::RawReader<R>,
                          completer: &Completer,
gwenn's avatar
gwenn committed
                          config: &Config)
                          -> Result<Option<KeyPress>> {
    // get a list of completions
gwenn's avatar
gwenn committed
    let (start, candidates) = try!(completer.complete(&s.line, s.line.pos()));
    // if no completions, we are done
    if candidates.is_empty() {
        try!(beep());
        Ok(None)
gwenn's avatar
gwenn committed
    } else if CompletionType::Circular == config.completion_type() {
        // Save the current edited line before to overwrite it
        s.backup();
        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();
gwenn's avatar
gwenn committed
            key = try!(rdr.next_key(false));
gwenn's avatar
gwenn committed
                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
                }
gwenn's avatar
gwenn committed
                KeyPress::Esc => {
gwenn's avatar
gwenn committed
                    // Re-show original buffer
                    s.snapshot();
                    if i < candidates.len() {
                        try!(s.refresh_line());
gwenn's avatar
gwenn committed
                    return Ok(None);
                }
                _ => {
                    break;
gwenn's avatar
gwenn committed
    } else if CompletionType::List == config.completion_type() {
        // beep if ambiguous
        if candidates.len() > 1 {
            try!(beep());
        }
        if let Some(lcp) = longest_common_prefix(&candidates) {
            // if we can extend the item, extend it and return to main loop
            if lcp.len() > s.line.pos() - start {
gwenn's avatar
gwenn committed
                completer.update(&mut s.line, start, lcp);
                try!(s.refresh_line());
                return Ok(None);
            }
        }
gwenn's avatar
gwenn committed
        // we can't complete any further, wait for second tab
gwenn's avatar
gwenn committed
        let mut key = try!(rdr.next_key(false));
gwenn's avatar
gwenn committed
        // if any character other than tab, pass it to the main loop
        if key != KeyPress::Tab {
gwenn's avatar
gwenn committed
            return Ok(Some(key));
gwenn's avatar
gwenn committed
        }
gwenn's avatar
gwenn committed
        // move cursor to EOL to avoid overwriting the command line
        let save_pos = s.line.pos();
        try!(edit_move_end(s));
        s.line.set_pos(save_pos);
gwenn's avatar
gwenn committed
        // we got a second tab, maybe show list of possible completions
gwenn's avatar
gwenn committed
        let mut show_completions = true;
        if candidates.len() > config.completion_prompt_limit() {
            let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len());
            try!(write_and_flush(s.out, msg.as_bytes()));
            s.old_rows += 1;
            while key != KeyPress::Char('y') && key != KeyPress::Char('Y') &&
                  key != KeyPress::Char('n') && key != KeyPress::Char('N') &&
                  key != KeyPress::Backspace {
                key = try!(rdr.next_key(true));
            }
            show_completions = match key {
                KeyPress::Char('y') |
                KeyPress::Char('Y') => true,
                _ => false,
            };
        }
        if show_completions {
gwenn's avatar
gwenn committed
            page_completions(rdr, s, &candidates)
gwenn's avatar
gwenn committed
        } else {
            try!(s.refresh_line());
            Ok(None)
        }
    } else {
        Ok(None)
gwenn's avatar
gwenn committed
fn page_completions<R: Read>(rdr: &mut tty::RawReader<R>,
                             s: &mut State,
                             candidates: &[String])
                             -> Result<Option<KeyPress>> {
    use std::cmp;
    use unicode_width::UnicodeWidthStr;

    let min_col_pad = 2;
    let max_width = cmp::min(s.cols,
                             candidates.into_iter()
                                 .map(|s| UnicodeWidthStr::width(s.as_str()))
                                 .max()
                                 .unwrap() + min_col_pad);
    let num_cols = s.cols / max_width;

    let mut pause_row = tty::get_rows(s.output_handle) - 1;
    let num_rows = (candidates.len() + num_cols - 1) / num_cols;
    let mut ab = String::new();
    for row in 0..num_rows {
        if row == pause_row {
            try!(write_and_flush(s.out, b"\n--More--"));
            let mut key = KeyPress::Null;
            while key != KeyPress::Char('y') && key != KeyPress::Char('Y') &&
                  key != KeyPress::Char('n') && key != KeyPress::Char('N') &&
                  key != KeyPress::Char('q') &&
                  key != KeyPress::Char('Q') &&
                  key != KeyPress::Char(' ') &&
                  key != KeyPress::Backspace && key != KeyPress::Enter {
                key = try!(rdr.next_key(true));
            }
            match key {
                KeyPress::Char('y') |
                KeyPress::Char('Y') |
                KeyPress::Char(' ') => {
                    pause_row += tty::get_rows(s.output_handle) - 1;
                }
                KeyPress::Enter => {
                    pause_row += 1;
                }
                _ => break,
            }
        } else {
            try!(write_and_flush(s.out, b"\n"));
        }
        ab.clear();
        for col in 0..num_cols {
            let i = (col * num_rows) + row;
            if i < candidates.len() {
                let candidate = &candidates[i];
                ab.push_str(candidate);
                let width = UnicodeWidthStr::width(candidate.as_str());
                if ((col + 1) * num_rows) + row < candidates.len() {
                    for _ in width..max_width {
                        ab.push(' ');
                    }
                }
            }
        }
        try!(write_and_flush(s.out, ab.as_bytes()));
    }
gwenn's avatar
gwenn committed
    // TODO
    Ok(None)
}

/// Incremental search
fn reverse_incremental_search<R: Read>(rdr: &mut tty::RawReader<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 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));

gwenn's avatar
gwenn committed
        key = try!(rdr.next_key(true));
        if let KeyPress::Char(c) = key {
            search_buf.push(c);
        } else {
            match key {
gwenn's avatar
gwenn committed
                KeyPress::Ctrl('H') |
                KeyPress::Backspace => {
                    search_buf.pop();
gwenn's avatar
gwenn committed
                    continue;
                }
gwenn's avatar
gwenn committed
                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') => {
gwenn's avatar
gwenn committed
                    reverse = false;
                    if history_idx < history.len() - 1 {
                        history_idx += 1;
                    } else {
                        success = false;
                        continue;
                    }
                }
gwenn's avatar
gwenn committed
                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,
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
#[allow(let_unit_value)]
fn readline_edit<C: Completer>(prompt: &str,
                               editor: &mut Editor<C>,
                               original_mode: tty::Mode)
                               -> Result<String> {
    let completer = editor.completer.as_ref().map(|c| c as &Completer);

    let mut stdout = io::stdout();
    let stdout_handle = try!(tty::stdout_handle());
    editor.kill_ring.reset();
    let mut s = State::new(&mut stdout, stdout_handle, prompt, editor.history.len());
    try!(s.refresh_line());
    let mut rdr = try!(tty::RawReader::new(io::stdin()));
gwenn's avatar
gwenn committed
        let rk = rdr.next_key(true);
        if rk.is_err() && tty::SIGWINCH.compare_and_swap(true, false, atomic::Ordering::SeqCst) {
            s.update_columns();
gwenn's avatar
gwenn committed
            try!(s.refresh_line());
            continue;
        }
        let mut key = try!(rk);
        if let KeyPress::Char(c) = key {
            editor.kill_ring.reset();
            try!(edit_insert(&mut s, c));
        // autocomplete
gwenn's avatar
gwenn committed
        if key == KeyPress::Tab && completer.is_some() {
gwenn's avatar
gwenn committed
            let next = try!(complete_line(&mut rdr, &mut s, completer.unwrap(), &editor.config));
            if next.is_some() {
                editor.kill_ring.reset();
                key = next.unwrap();
                if let KeyPress::Char(c) = key {
                    try!(edit_insert(&mut s, c));
gwenn's avatar
gwenn committed
        } else if key == KeyPress::Ctrl('R') {
gwenn's avatar
gwenn committed
            // Search history backward
            let next = try!(reverse_incremental_search(&mut rdr, &mut s, &editor.history));
gwenn's avatar
gwenn committed
            if next.is_some() {
                key = next.unwrap();
gwenn's avatar
gwenn committed
            } else {
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
        } else if key == KeyPress::UnknownEscSeq {
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('A') |
            KeyPress::Home => {
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                // Move to the beginning of line.
                try!(edit_move_home(&mut s))
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('B') |
            KeyPress::Left => {
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                // Move back a character.
                try!(edit_move_left(&mut s))
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('C') => {
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                return Err(error::ReadlineError::Interrupted);
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('D') => {
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                if s.line.is_empty() {
gwenn's avatar
gwenn committed
                    return Err(error::ReadlineError::Eof);
                } else {
gwenn's avatar
gwenn committed
                    // Delete (forward) one character at point.
                    try!(edit_delete(&mut s))
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('E') |
            KeyPress::End => {
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                // Move to the end of line.
                try!(edit_move_end(&mut s))
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('F') |
            KeyPress::Right => {
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                // Move forward a character.
                try!(edit_move_right(&mut s))
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('H') |
            KeyPress::Backspace => {
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                // Delete one character backward.
                try!(edit_backspace(&mut s))
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('K') => {
gwenn's avatar
gwenn committed
                // Kill the text from point to the end of the line.
gwenn's avatar
gwenn committed
                if let Some(text) = try!(edit_kill_line(&mut s)) {
                    editor.kill_ring.kill(&text, true)
gwenn's avatar
gwenn committed
                }
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('L') => {
gwenn's avatar
gwenn committed
                // Clear the screen leaving the current line at the top of the screen.
gwenn's avatar
gwenn committed
                try!(tty::clear_screen(&mut s.out, s.output_handle));
                try!(s.refresh_line())
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('N') |
            KeyPress::Down => {
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                // Fetch the next command from the history list.
                try!(edit_history_next(&mut s, &editor.history, false))
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('P') |
            KeyPress::Up => {
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                // Fetch the previous command from the history list.
                try!(edit_history_next(&mut s, &editor.history, true))
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('T') => {
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                // Exchange the char before cursor with the character at cursor.
                try!(edit_transpose_chars(&mut s))
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('U') => {
gwenn's avatar
gwenn committed
                // Kill backward from point to the beginning of the line.
gwenn's avatar
gwenn committed
                if let Some(text) = try!(edit_discard_line(&mut s)) {
                    editor.kill_ring.kill(&text, false)
gwenn's avatar
gwenn committed
                }
            }
gwenn's avatar
gwenn committed
            #[cfg(unix)]
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('V') => {
                // Quoted insert
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                let c = try!(rdr.next_char());
                try!(edit_insert(&mut s, c)) // FIXME
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('W') => {
gwenn's avatar
gwenn committed
                // Kill the word behind point, using white space as a word boundary
gwenn's avatar
gwenn committed
                if let Some(text) = try!(edit_delete_prev_word(&mut s, char::is_whitespace)) {
                    editor.kill_ring.kill(&text, false)
gwenn's avatar
gwenn committed
                }
            }
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('Y') => {
gwenn's avatar
gwenn committed
                // retrieve (yank) last item killed
                if let Some(text) = editor.kill_ring.yank() {
gwenn's avatar
gwenn committed
                    try!(edit_yank(&mut s, text))
gwenn's avatar
gwenn committed
                }
            }
gwenn's avatar
gwenn committed
            #[cfg(unix)]
gwenn's avatar
gwenn committed
            KeyPress::Ctrl('Z') => {
                try!(tty::disable_raw_mode(original_mode));
gwenn's avatar
gwenn committed
                try!(signal::raise(signal::SIGSTOP));
                try!(tty::enable_raw_mode()); // TODO original_mode may have changed
gwenn's avatar
gwenn committed
                try!(s.refresh_line())
            }
            // TODO CTRL-_ // undo
gwenn's avatar
gwenn committed
            KeyPress::Enter |
            KeyPress::Ctrl('J') => {
gwenn's avatar
gwenn committed
                // Accept the line regardless of where the cursor is.
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                try!(edit_move_end(&mut s));
                break;
            }
gwenn's avatar
gwenn committed
            KeyPress::Meta('\x08') |
            KeyPress::Meta('\x7f') => {
gwenn's avatar
gwenn committed
                // kill one word backward
gwenn's avatar
gwenn committed
                // Kill from the cursor to the start of the current word, or, if between words, to the start of the previous word.
gwenn's avatar
gwenn committed
                if let Some(text) = try!(edit_delete_prev_word(&mut s,
                                                               |ch| !ch.is_alphanumeric())) {
                    editor.kill_ring.kill(&text, false)
gwenn's avatar
gwenn committed
                }
            }
            KeyPress::Meta('<') => {
                // move to first entry in history
                editor.kill_ring.reset();
                try!(edit_history(&mut s, &editor.history, true))
            }
            KeyPress::Meta('>') => {
                // move to last entry in history
                editor.kill_ring.reset();
                try!(edit_history(&mut s, &editor.history, false))
gwenn's avatar
gwenn committed
            KeyPress::Meta('B') => {
gwenn's avatar
gwenn committed
                // move backwards one word
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                try!(edit_move_to_prev_word(&mut s))
            }
gwenn's avatar
gwenn committed
            KeyPress::Meta('C') => {
gwenn's avatar
gwenn committed
                // capitalize word after point
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                try!(edit_word(&mut s, WordAction::CAPITALIZE))
            }
gwenn's avatar
gwenn committed
            KeyPress::Meta('D') => {
gwenn's avatar
gwenn committed
                // kill one word forward
                if let Some(text) = try!(edit_delete_word(&mut s)) {
                    editor.kill_ring.kill(&text, true)
gwenn's avatar
gwenn committed
                }
            }
gwenn's avatar
gwenn committed
            KeyPress::Meta('F') => {
gwenn's avatar
gwenn committed
                // move forwards one word
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                try!(edit_move_to_next_word(&mut s))
            }
gwenn's avatar
gwenn committed
            KeyPress::Meta('L') => {
gwenn's avatar
gwenn committed
                // lowercase word after point
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                try!(edit_word(&mut s, WordAction::LOWERCASE))
            }
gwenn's avatar
gwenn committed
            KeyPress::Meta('T') => {
gwenn's avatar
gwenn committed
                // transpose words
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                try!(edit_transpose_words(&mut s))
            }
gwenn's avatar
gwenn committed
            KeyPress::Meta('U') => {
gwenn's avatar
gwenn committed
                // uppercase word after point
                editor.kill_ring.reset();
gwenn's avatar
gwenn committed
                try!(edit_word(&mut s, WordAction::UPPERCASE))
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
            KeyPress::Meta('Y') => {