Skip to content
Snippets Groups Projects
lib.rs 45.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
//! ```
//! let mut rl = rustyline::Editor::new();
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(unicode)]
gwenn's avatar
gwenn committed
#![allow(unknown_lints)]
gwenn's avatar
gwenn committed

gwenn's avatar
gwenn committed
#[cfg(windows)]
extern crate kernel32;
extern crate libc;
gwenn's avatar
gwenn committed
#[macro_use]
extern crate log;
gwenn's avatar
gwenn committed
#[cfg(unix)]
extern crate nix;
extern crate std_unicode;
gwenn's avatar
gwenn committed
extern crate unicode_segmentation;
extern crate unicode_width;
#[cfg(windows)]
extern crate winapi;
mod consts;
pub mod error;
gwenn's avatar
gwenn committed
pub mod hint;
pub mod history;
mod keymap;
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
mod undo;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
gwenn's avatar
gwenn committed
use std::io::{self, Write};
use std::result;
use unicode_segmentation::UnicodeSegmentation;
gwenn's avatar
gwenn committed
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

gwenn's avatar
gwenn committed
use tty::{Position, RawMode, RawReader, Renderer, Term, Terminal};
gwenn's avatar
gwenn committed
use completion::{longest_common_prefix, Completer};
gwenn's avatar
gwenn committed
use hint::Hinter;
use history::{Direction, History};
gwenn's avatar
gwenn committed
use line_buffer::{LineBuffer, WordAction, MAX_LINE};
pub use keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
use keymap::EditState;
gwenn's avatar
gwenn committed
use kill_ring::{KillRing, Mode};
pub use config::{CompletionType, Config, EditMode, HistoryDuplicates};
gwenn's avatar
gwenn committed
use undo::Changeset;
pub use consts::KeyPress;
/// The error type for I/O and Linux Syscalls (Errno)
pub type Result<T> = result::Result<T, error::ReadlineError>;

gwenn's avatar
gwenn committed
/// Represent the state during line editing.
/// Implement rendering.
struct State<'out, 'prompt> {
gwenn's avatar
gwenn committed
    out: &'out mut Renderer,
gwenn's avatar
gwenn committed
    prompt: &'prompt str,  // Prompt to display
    prompt_size: Position, // Prompt Unicode/visible width and height
    line: LineBuffer,      // Edited line buffer
    cursor: Position,      /* Cursor position (relative to the start of the prompt
                            * for `row`) */
    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
    saved_line_for_history: LineBuffer, // Current edited line before history browsing
gwenn's avatar
gwenn committed
    byte_buffer: [u8; 4],
gwenn's avatar
gwenn committed
    edit_state: EditState,
gwenn's avatar
gwenn committed
    changes: Rc<RefCell<Changeset>>,
gwenn's avatar
gwenn committed
    hinter: Option<Rc<RefCell<Hinter>>>,
impl<'out, 'prompt> State<'out, 'prompt> {
gwenn's avatar
gwenn committed
    fn new(
        out: &'out mut Renderer,
        config: &Config,
        prompt: &'prompt str,
        history_index: usize,
        custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>,
gwenn's avatar
gwenn committed
        hinter: Option<Rc<RefCell<Hinter>>>,
gwenn's avatar
gwenn committed
    ) -> State<'out, 'prompt> {
        let capacity = MAX_LINE;
gwenn's avatar
gwenn committed
        let prompt_size = out.calculate_position(prompt, Position::default());
            out: out,
            prompt: prompt,
            prompt_size: prompt_size,
            line: LineBuffer::with_capacity(capacity),
            cursor: prompt_size,
            old_rows: prompt_size.row,
            history_index: history_index,
gwenn's avatar
gwenn committed
            saved_line_for_history: LineBuffer::with_capacity(capacity),
gwenn's avatar
gwenn committed
            byte_buffer: [0; 4],
            edit_state: EditState::new(config, custom_bindings),
gwenn's avatar
gwenn committed
            changes: Rc::new(RefCell::new(Changeset::new())),
gwenn's avatar
gwenn committed
            hinter: hinter,
gwenn's avatar
gwenn committed
    fn next_cmd<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> {
gwenn's avatar
gwenn committed
        loop {
gwenn's avatar
gwenn committed
            let rc = self.edit_state.next_cmd(rdr);
gwenn's avatar
gwenn committed
            if rc.is_err() && self.out.sigwinch() {
                self.out.update_size();
gwenn's avatar
gwenn committed
                try!(self.refresh_line());
                continue;
            }
            return rc;
gwenn's avatar
gwenn committed
    fn backup(&mut self) {
gwenn's avatar
gwenn committed
        self.saved_line_for_history
            .update(self.line.as_str(), self.line.pos());
gwenn's avatar
gwenn committed
    }
    fn restore(&mut self) {
gwenn's avatar
gwenn committed
        self.line.update(
            self.saved_line_for_history.as_str(),
            self.saved_line_for_history.pos(),
        );
gwenn's avatar
gwenn committed
    }
gwenn's avatar
gwenn committed
    fn move_cursor(&mut self) -> Result<()> {
        // calculate the desired position of the cursor
gwenn's avatar
gwenn committed
        let cursor = self.out
            .calculate_position(&self.line[..self.line.pos()], self.prompt_size);
gwenn's avatar
gwenn committed
        if self.cursor == cursor {
            return Ok(());
        }
        try!(self.out.move_cursor(self.cursor, cursor));
        self.cursor = cursor;
        Ok(())
    }

    /// 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;
gwenn's avatar
gwenn committed
        let hint = self.hint();
        self.refresh(self.prompt, prompt_size, hint)
    }

    fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> {
gwenn's avatar
gwenn committed
        let prompt_size = self.out.calculate_position(prompt, Position::default());
gwenn's avatar
gwenn committed
        let hint = self.hint();
        self.refresh(prompt, prompt_size, hint)
gwenn's avatar
gwenn committed
    fn refresh(&mut self, prompt: &str, prompt_size: Position, hint: Option<String>) -> Result<()> {
gwenn's avatar
gwenn committed
        let (cursor, end_pos) = try!(self.out.refresh_line(
            prompt,
            prompt_size,
            &self.line,
gwenn's avatar
gwenn committed
            hint,
gwenn's avatar
gwenn committed
            self.cursor.row,
            self.old_rows,
        ));

        self.cursor = cursor;
gwenn's avatar
gwenn committed
        self.old_rows = end_pos.row;
gwenn's avatar
gwenn committed
        Ok(())
gwenn's avatar
gwenn committed

    fn hint(&self) -> Option<String> {
        if let Some(ref hinter) = self.hinter {
gwenn's avatar
gwenn committed
            hinter
                .borrow_mut()
                .hint(self.line.as_str(), self.line.pos())
gwenn's avatar
gwenn committed
        } else {
            None
        }
    }
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)
gwenn's avatar
gwenn committed
            .field("cols", &self.out.get_columns())
            .field("old_rows", &self.old_rows)
gwenn's avatar
gwenn committed
            .field("history_index", &self.history_index)
gwenn's avatar
gwenn committed
            .field("saved_line_for_history", &self.saved_line_for_history)
gwenn's avatar
gwenn committed
            .finish()
/// Insert the character `ch` at cursor current position.
gwenn's avatar
gwenn committed
fn edit_insert(s: &mut State, ch: char, n: RepeatCount) -> Result<()> {
gwenn's avatar
gwenn committed
    if let Some(push) = s.line.insert(ch, n) {
gwenn's avatar
gwenn committed
        if push {
gwenn's avatar
gwenn committed
            let prompt_size = s.prompt_size;
            let hint = s.hint();
gwenn's avatar
gwenn committed
            if n == 1 && s.cursor.col + ch.width().unwrap_or(0) < s.out.get_columns() &&
                hint.is_none()
            {
                // Avoid a full update of the line in the trivial case.
gwenn's avatar
gwenn committed
                let cursor = s.out
                    .calculate_position(&s.line[..s.line.pos()], s.prompt_size);
gwenn's avatar
gwenn committed
                let bits = ch.encode_utf8(&mut s.byte_buffer);
                let bits = bits.as_bytes();
gwenn's avatar
gwenn committed
                s.out.write_and_flush(bits)
gwenn's avatar
gwenn committed
                s.refresh(s.prompt, prompt_size, hint)
            s.refresh_line()
gwenn's avatar
gwenn committed
/// Replace a single (or n) character(s) under the cursor (Vi mode)
gwenn's avatar
gwenn committed
fn edit_replace_char(s: &mut State, ch: char, n: RepeatCount) -> Result<()> {
gwenn's avatar
gwenn committed
    s.changes.borrow_mut().begin();
    let succeed = if let Some(chars) = s.line.delete(n) {
        let count = chars.graphemes(true).count();
gwenn's avatar
gwenn committed
        s.line.insert(ch, count);
        s.line.move_backward(1);
gwenn's avatar
gwenn committed
        true
gwenn's avatar
gwenn committed
    } else {
gwenn's avatar
gwenn committed
        false
    };
    s.changes.borrow_mut().end();
gwenn's avatar
gwenn committed
    if succeed {
        s.refresh_line()
    } else {
        Ok(())
    }
gwenn's avatar
gwenn committed
/// Overwrite the character under the cursor (Vi mode)
fn edit_overwrite_char(s: &mut State, ch: char) -> Result<()> {
    if let Some(end) = s.line.next_pos(1) {
        {
            let text = ch.encode_utf8(&mut s.byte_buffer);
            let start = s.line.pos();
            s.line.replace(start..end, text);
        }
        s.refresh_line()
    } else {
        Ok(())
    }
}

gwenn's avatar
gwenn committed
// Yank/paste `text` at current position.
gwenn's avatar
gwenn committed
fn edit_yank(s: &mut State, text: &str, anchor: Anchor, n: RepeatCount) -> Result<()> {
gwenn's avatar
gwenn committed
    if let Anchor::After = anchor {
        s.line.move_forward(1);
    }
    if s.line.yank(text, n).is_some() {
gwenn's avatar
gwenn committed
        if !s.edit_state.is_emacs_mode() {
            s.line.move_backward(1);
gwenn's avatar
gwenn committed
        }
gwenn's avatar
gwenn committed
        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.changes.borrow_mut().begin();
gwenn's avatar
gwenn committed
    s.line.yank_pop(yank_size, text);
gwenn's avatar
gwenn committed
    let result = edit_yank(s, text, Anchor::Before, 1);
    s.changes.borrow_mut().end();
    result
gwenn's avatar
gwenn committed
}

/// Move cursor on the left.
fn edit_move_backward(s: &mut State, n: RepeatCount) -> Result<()> {
    if s.line.move_backward(n) {
gwenn's avatar
gwenn committed
        s.move_cursor()
    } else {
        Ok(())
    }
}

/// Move cursor on the right.
fn edit_move_forward(s: &mut State, n: RepeatCount) -> Result<()> {
    if s.line.move_forward(n) {
gwenn's avatar
gwenn committed
        s.move_cursor()
    }
}

/// 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() {
gwenn's avatar
gwenn committed
        s.move_cursor()
    } 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() {
gwenn's avatar
gwenn committed
        s.move_cursor()
gwenn's avatar
gwenn committed
    } 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.
gwenn's avatar
gwenn committed
fn edit_delete(s: &mut State, n: RepeatCount) -> Result<()> {
    if s.line.delete(n).is_some() {
        s.refresh_line()
    } else {
        Ok(())
    }
}

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

/// Kill the text from point to the end of the line.
fn edit_kill_line(s: &mut State) -> Result<()> {
    if s.line.kill_line() {
        s.refresh_line()
    }
}

/// Kill backward from point to the beginning of the line.
fn edit_discard_line(s: &mut State) -> Result<()> {
    if s.line.discard_line() {
        s.refresh_line()
    }
}

/// Exchange the char before cursor with the character at cursor.
fn edit_transpose_chars(s: &mut State) -> Result<()> {
gwenn's avatar
gwenn committed
    s.changes.borrow_mut().begin();
    let succeed = s.line.transpose_chars();
    s.changes.borrow_mut().end();
gwenn's avatar
gwenn committed
    if succeed {
        s.refresh_line()
    } else {
        Ok(())
    }
gwenn's avatar
gwenn committed
fn edit_move_to_prev_word(s: &mut State, word_def: Word, n: RepeatCount) -> Result<()> {
gwenn's avatar
gwenn committed
    if s.line.move_to_prev_word(word_def, n) {
gwenn's avatar
gwenn committed
        s.move_cursor()
gwenn's avatar
gwenn committed
    } else {
        Ok(())
    }
}

/// Delete the previous word, maintaining the cursor at the start of the
/// current word.
fn edit_delete_prev_word(s: &mut State, word_def: Word, n: RepeatCount) -> Result<()> {
    if s.line.delete_prev_word(word_def, n) {
        s.refresh_line()
gwenn's avatar
gwenn committed
fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word, n: RepeatCount) -> Result<()> {
gwenn's avatar
gwenn committed
    if s.line.move_to_next_word(at, word_def, n) {
gwenn's avatar
gwenn committed
        s.move_cursor()
gwenn's avatar
gwenn committed
    } else {
        Ok(())
    }
}

gwenn's avatar
gwenn committed
fn edit_move_to(s: &mut State, cs: CharSearch, n: RepeatCount) -> Result<()> {
gwenn's avatar
gwenn committed
    if s.line.move_to(cs, n) {
gwenn's avatar
gwenn committed
        s.move_cursor()
gwenn's avatar
gwenn committed
    } else {
        Ok(())
    }
}

gwenn's avatar
gwenn committed
/// 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, at: At, word_def: Word, n: RepeatCount) -> Result<()> {
    if s.line.delete_word(at, word_def, n) {
        s.refresh_line()
gwenn's avatar
gwenn committed
    } else {
fn edit_delete_to(s: &mut State, cs: CharSearch, n: RepeatCount) -> Result<()> {
    if s.line.delete_to(cs, n) {
        s.refresh_line()
gwenn's avatar
gwenn committed
    } else {
gwenn's avatar
gwenn committed
fn edit_word(s: &mut State, a: WordAction) -> Result<()> {
gwenn's avatar
gwenn committed
    s.changes.borrow_mut().begin();
    let succeed = s.line.edit_word(a);
    s.changes.borrow_mut().end();
gwenn's avatar
gwenn committed
    if succeed {
        s.refresh_line()
    } else {
        Ok(())
    }
gwenn's avatar
gwenn committed
}

gwenn's avatar
gwenn committed
fn edit_transpose_words(s: &mut State, n: RepeatCount) -> Result<()> {
gwenn's avatar
gwenn committed
    s.changes.borrow_mut().begin();
    let succeed = s.line.transpose_words(n);
    s.changes.borrow_mut().end();
gwenn's avatar
gwenn committed
    if succeed {
        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 overwriting it
            s.backup();
        } 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.changes.borrow_mut().begin();
gwenn's avatar
gwenn committed
        s.line.update(buf, buf.len());
gwenn's avatar
gwenn committed
        s.changes.borrow_mut().end();
gwenn's avatar
gwenn committed
    } else {
        // Restore current edited line
gwenn's avatar
gwenn committed
        s.restore();
gwenn's avatar
gwenn committed
// Non-incremental, anchored search
fn edit_history_search(s: &mut State, history: &History, dir: Direction) -> Result<()> {
    if history.is_empty() {
gwenn's avatar
gwenn committed
        return s.out.beep();
gwenn's avatar
gwenn committed
    if s.history_index == history.len() && dir == Direction::Forward {
gwenn's avatar
gwenn committed
        return s.out.beep();
    } else if s.history_index == 0 && dir == Direction::Reverse {
gwenn's avatar
gwenn committed
        return s.out.beep();
    }
    if dir == Direction::Reverse {
        s.history_index -= 1;
    } else {
        s.history_index += 1;
    }
    if let Some(history_index) =
gwenn's avatar
gwenn committed
        history.starts_with(&s.line.as_str()[..s.line.pos()], s.history_index, dir)
    {
        s.history_index = history_index;
        let buf = history.get(history_index).unwrap();
gwenn's avatar
gwenn committed
        s.changes.borrow_mut().begin();
        s.line.update(buf, buf.len());
gwenn's avatar
gwenn committed
        s.changes.borrow_mut().end();
        s.refresh_line()
    } else {
gwenn's avatar
gwenn committed
        s.out.beep()
/// 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 {
gwenn's avatar
gwenn committed
            // Save the current edited line before overwriting it
            s.backup();
        } 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();
gwenn's avatar
gwenn committed
        s.changes.borrow_mut().begin();
        s.line.update(buf, buf.len());
gwenn's avatar
gwenn committed
        s.changes.borrow_mut().end();
    } else {
        s.history_index = history.len();
        // Restore current edited line
gwenn's avatar
gwenn committed
        s.restore();
gwenn's avatar
gwenn committed
    s.refresh_line()
/// Completes the line/word
gwenn's avatar
gwenn committed
fn complete_line<R: RawReader, C: Completer + ?Sized>(
gwenn's avatar
gwenn committed
    rdr: &mut R,
    s: &mut State,
    completer: &mut C,
gwenn's avatar
gwenn committed
    config: &Config,
) -> Result<Option<Cmd>> {
    // 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() {
gwenn's avatar
gwenn committed
        try!(s.out.beep());
gwenn's avatar
gwenn committed
    } else if CompletionType::Circular == config.completion_type() {
gwenn's avatar
gwenn committed
        let mark = s.changes.borrow_mut().begin();
gwenn's avatar
gwenn committed
        // Save the current edited line before overwriting it
        let backup = s.line.as_str().to_owned();
        let backup_pos = s.line.pos();
gwenn's avatar
gwenn committed
        let mut cmd;
        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
gwenn's avatar
gwenn committed
                s.line.update(&backup, backup_pos);
                try!(s.refresh_line());
gwenn's avatar
gwenn committed
            cmd = try!(s.next_cmd(rdr));
gwenn's avatar
gwenn committed
            match cmd {
                Cmd::Complete => {
gwenn's avatar
gwenn committed
                    i = (i + 1) % (candidates.len() + 1); // Circular
                    if i == candidates.len() {
gwenn's avatar
gwenn committed
                        try!(s.out.beep());
gwenn's avatar
gwenn committed
                }
gwenn's avatar
gwenn committed
                Cmd::Abort => {
gwenn's avatar
gwenn committed
                    // Re-show original buffer
                    if i < candidates.len() {
gwenn's avatar
gwenn committed
                        s.line.update(&backup, backup_pos);
                        try!(s.refresh_line());
gwenn's avatar
gwenn committed
                    s.changes.borrow_mut().truncate(mark);
gwenn's avatar
gwenn committed
                    return Ok(None);
                }
                _ => {
gwenn's avatar
gwenn committed
                    s.changes.borrow_mut().end();
gwenn's avatar
gwenn committed
                    break;
gwenn's avatar
gwenn committed
        Ok(Some(cmd))
gwenn's avatar
gwenn committed
    } else if CompletionType::List == config.completion_type() {
        // beep if ambiguous
        if candidates.len() > 1 {
gwenn's avatar
gwenn committed
            try!(s.out.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 cmd = try!(s.next_cmd(rdr));
gwenn's avatar
gwenn committed
        // if any character other than tab, pass it to the main loop
gwenn's avatar
gwenn committed
        if cmd != Cmd::Complete {
            return Ok(Some(cmd));
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 show_completions = if candidates.len() > config.completion_prompt_limit() {
gwenn's avatar
gwenn committed
            let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len());
gwenn's avatar
gwenn committed
            try!(s.out.write_and_flush(msg.as_bytes()));
gwenn's avatar
gwenn committed
            s.old_rows += 1;
gwenn's avatar
gwenn committed
            while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y') &&
gwenn's avatar
gwenn committed
                cmd != Cmd::SelfInsert(1, 'n') &&
                cmd != Cmd::SelfInsert(1, 'N') &&
                cmd != Cmd::Kill(Movement::BackwardChar(1))
            {
gwenn's avatar
gwenn committed
                cmd = try!(s.next_cmd(rdr));
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
            match cmd {
gwenn's avatar
gwenn committed
                Cmd::SelfInsert(1, 'y') | Cmd::SelfInsert(1, 'Y') => true,
gwenn's avatar
gwenn committed
                _ => false,
gwenn's avatar
gwenn committed
            }
        } else {
            true
        };
gwenn's avatar
gwenn committed
        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: RawReader>(
    rdr: &mut R,
    s: &mut State,
    candidates: &[String],
) -> Result<Option<Cmd>> {
gwenn's avatar
gwenn committed
    use std::cmp;

    let min_col_pad = 2;
gwenn's avatar
gwenn committed
    let cols = s.out.get_columns();
gwenn's avatar
gwenn committed
    let max_width = cmp::min(
        cols,
        candidates
            .into_iter()
            .map(|s| s.as_str().width())
            .max()
            .unwrap() + min_col_pad,
    );
gwenn's avatar
gwenn committed
    let num_cols = cols / max_width;
gwenn's avatar
gwenn committed

gwenn's avatar
gwenn committed
    let mut pause_row = s.out.get_rows() - 1;
gwenn's avatar
gwenn committed
    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 {
gwenn's avatar
gwenn committed
            try!(s.out.write_and_flush(b"\n--More--"));
gwenn's avatar
gwenn committed
            let mut cmd = Cmd::Noop;
gwenn's avatar
gwenn committed
            while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y') &&
gwenn's avatar
gwenn committed
                cmd != Cmd::SelfInsert(1, 'n') &&
gwenn's avatar
gwenn committed
                cmd != Cmd::SelfInsert(1, 'N') &&
gwenn's avatar
gwenn committed
                cmd != Cmd::SelfInsert(1, 'q') &&
                cmd != Cmd::SelfInsert(1, 'Q') &&
                cmd != Cmd::SelfInsert(1, ' ') &&
                cmd != Cmd::Kill(Movement::BackwardChar(1)) &&
                cmd != Cmd::AcceptLine
            {
gwenn's avatar
gwenn committed
                cmd = try!(s.next_cmd(rdr));
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
            match cmd {
gwenn's avatar
gwenn committed
                Cmd::SelfInsert(1, 'y') | Cmd::SelfInsert(1, 'Y') | Cmd::SelfInsert(1, ' ') => {
gwenn's avatar
gwenn committed
                    pause_row += s.out.get_rows() - 1;
gwenn's avatar
gwenn committed
                }
gwenn's avatar
gwenn committed
                Cmd::AcceptLine => {
gwenn's avatar
gwenn committed
                    pause_row += 1;
                }
                _ => break,
            }
gwenn's avatar
gwenn committed
            try!(s.out.write_and_flush(b"\n"));
gwenn's avatar
gwenn committed
        } else {
gwenn's avatar
gwenn committed
            try!(s.out.write_and_flush(b"\n"));
gwenn's avatar
gwenn committed
        }
        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);
gwenn's avatar
gwenn committed
                let width = candidate.as_str().width();
gwenn's avatar
gwenn committed
                if ((col + 1) * num_rows) + row < candidates.len() {
                    for _ in width..max_width {
                        ab.push(' ');
                    }
                }
            }
        }
gwenn's avatar
gwenn committed
        try!(s.out.write_and_flush(ab.as_bytes()));
gwenn's avatar
gwenn committed
    }
gwenn's avatar
gwenn committed
    try!(s.out.write_and_flush(b"\n"));
gwenn's avatar
gwenn committed
    try!(s.refresh_line());
gwenn's avatar
gwenn committed
    Ok(None)
}

/// Incremental search
gwenn's avatar
gwenn committed
fn reverse_incremental_search<R: RawReader>(
    rdr: &mut R,
    s: &mut State,
    history: &History,
) -> Result<Option<Cmd>> {
    if history.is_empty() {
        return Ok(None);
    }
gwenn's avatar
gwenn committed
    let mark = s.changes.borrow_mut().begin();
    // Save the current edited line (and cursor position) before overwriting it
    let backup = s.line.as_str().to_owned();
    let backup_pos = s.line.pos();

    let mut search_buf = String::new();
    let mut history_idx = history.len() - 1;
    let mut direction = Direction::Reverse;
    let mut success = true;

gwenn's avatar
gwenn committed
    let mut cmd;
    // 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
        cmd = try!(s.next_cmd(rdr));
gwenn's avatar
gwenn committed
        if let Cmd::SelfInsert(_, c) = cmd {
            search_buf.push(c);
gwenn's avatar
gwenn committed
            match cmd {
                Cmd::Kill(Movement::BackwardChar(_)) => {
                    search_buf.pop();
gwenn's avatar
gwenn committed
                    continue;
                }
gwenn's avatar
gwenn committed
                Cmd::ReverseSearchHistory => {
                    direction = Direction::Reverse;
                    if history_idx > 0 {
                        history_idx -= 1;
                    } else {
                        success = false;
                        continue;
                    }
gwenn's avatar
gwenn committed
                }
gwenn's avatar
gwenn committed
                Cmd::ForwardSearchHistory => {
                    direction = Direction::Forward;
gwenn's avatar
gwenn committed
                    if history_idx < history.len() - 1 {
                        history_idx += 1;
                    } else {
                        success = false;
                        continue;
                    }
                }
gwenn's avatar
gwenn committed
                Cmd::Abort => {
gwenn's avatar
gwenn committed
                    // Restore current edited line (before search)
gwenn's avatar
gwenn committed
                    s.line.update(&backup, backup_pos);
                    try!(s.refresh_line());
gwenn's avatar
gwenn committed
                    s.changes.borrow_mut().truncate(mark);
gwenn's avatar
gwenn committed
                    return Ok(None);
                }
                _ => break,
        success = match history.search(&search_buf, history_idx, direction) {
            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
    s.changes.borrow_mut().end();
gwenn's avatar
gwenn committed
    Ok(Some(cmd))
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(
gwenn's avatar
gwenn committed
    prompt: &str,
gwenn's avatar
gwenn committed
    initial: Option<(&str, &str)>,
    editor: &mut Editor,
gwenn's avatar
gwenn committed
    original_mode: tty::Mode,
) -> Result<String> {
    let completer = editor.completer.as_ref();
gwenn's avatar
gwenn committed
    let hinter = editor.hinter.as_ref().cloned();
    let mut stdout = editor.term.create_writer();
    editor.reset_kill_ring();
gwenn's avatar
gwenn committed
    let mut s = State::new(
        &mut stdout,
        &editor.config,
        prompt,
        editor.history.len(),
        editor.custom_bindings.clone(),
gwenn's avatar
gwenn committed
        hinter,
gwenn's avatar
gwenn committed
    );
    s.line.set_delete_listener(editor.kill_ring.clone());
    s.line.set_change_listener(s.changes.clone());
gwenn's avatar
gwenn committed
    if let Some((left, right)) = initial {
gwenn's avatar
gwenn committed
        s.line
            .update((left.to_owned() + right).as_ref(), left.len());
    try!(s.refresh_line());
gwenn's avatar
gwenn committed
    let mut rdr = try!(editor.term.create_reader(&editor.config));
gwenn's avatar
gwenn committed
        let rc = s.next_cmd(&mut rdr);
gwenn's avatar
gwenn committed
        let mut cmd = try!(rc);
gwenn's avatar
gwenn committed
        if cmd.should_reset_kill_ring() {
            editor.reset_kill_ring();
        }

        // autocomplete
gwenn's avatar
gwenn committed
        if cmd == Cmd::Complete && completer.is_some() {
            use std::ops::DerefMut;
gwenn's avatar
gwenn committed
            let next = try!(complete_line(
                &mut rdr,
                &mut s,
                completer.unwrap().borrow_mut().deref_mut(),
gwenn's avatar
gwenn committed
                &editor.config,
            ));
            if next.is_some() {
gwenn's avatar
gwenn committed
                cmd = next.unwrap();
gwenn's avatar
gwenn committed
        }

gwenn's avatar
gwenn committed
        if let Cmd::SelfInsert(n, c) = cmd {
            try!(edit_insert(&mut s, c, n));
gwenn's avatar
gwenn committed
            continue;
gwenn's avatar
gwenn committed
        } else if let Cmd::Insert(n, text) = cmd {
            try!(edit_yank(&mut s, &text, Anchor::Before, n));
            continue;
gwenn's avatar
gwenn committed
        }

        if cmd == Cmd::ReverseSearchHistory {
gwenn's avatar
gwenn committed
            // Search history backward
gwenn's avatar
gwenn committed
            let next = try!(reverse_incremental_search(
                &mut rdr,
                &mut s,
                &editor.history,
            ));
gwenn's avatar
gwenn committed
            if next.is_some() {
gwenn's avatar
gwenn committed
                cmd = next.unwrap();
gwenn's avatar
gwenn committed
            } else {
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
        match cmd {
gwenn's avatar
gwenn committed
            Cmd::Move(Movement::BeginningOfLine) => {
gwenn's avatar
gwenn committed
                // Move to the beginning of line.
                try!(edit_move_home(&mut s))
            }
gwenn's avatar
gwenn committed
            Cmd::Move(Movement::ViFirstPrint) => {
gwenn's avatar
gwenn committed
                try!(edit_move_home(&mut s));
                try!(edit_move_to_next_word(&mut s, At::Start, Word::Big, 1))
            }
gwenn's avatar
gwenn committed
            Cmd::Move(Movement::BackwardChar(n)) => {
gwenn's avatar
gwenn committed
                // Move back a character.
                try!(edit_move_backward(&mut s, n))
gwenn's avatar
gwenn committed
            }
            Cmd::Kill(Movement::ForwardChar(n)) => {
gwenn's avatar
gwenn committed
                // Delete (forward) one character at point.
gwenn's avatar
gwenn committed
                try!(edit_delete(&mut s, n))
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
            Cmd::Replace(n, c) => {
                try!(edit_replace_char(&mut s, c, n));
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
            Cmd::Overwrite(c) => {
                try!(edit_overwrite_char(&mut s, c));
            }
gwenn's avatar
gwenn committed
            Cmd::EndOfFile => if !s.edit_state.is_emacs_mode() && !s.line.is_empty() {
                try!(edit_move_end(&mut s));
                break;
            } else if s.line.is_empty() {
                return Err(error::ReadlineError::Eof);
            } else {
                try!(edit_delete(&mut s, 1))
            },
gwenn's avatar
gwenn committed
            Cmd::Move(Movement::EndOfLine) => {
gwenn's avatar
gwenn committed
                // Move to the end of line.
                try!(edit_move_end(&mut s))
            }
gwenn's avatar
gwenn committed
            Cmd::Move(Movement::ForwardChar(n)) => {
gwenn's avatar
gwenn committed
                // Move forward a character.
                try!(edit_move_forward(&mut s, n))
gwenn's avatar
gwenn committed
            }
            Cmd::Kill(Movement::BackwardChar(n)) => {
gwenn's avatar
gwenn committed
                // Delete one character backward.
gwenn's avatar
gwenn committed
                try!(edit_backspace(&mut s, n))
gwenn's avatar
gwenn committed
            }
            Cmd::Kill(Movement::EndOfLine) => {
gwenn's avatar
gwenn committed
                // Kill the text from point to the end of the line.
                editor.kill_ring.borrow_mut().start_killing();
gwenn's avatar
gwenn committed
                try!(edit_kill_line(&mut s));
                editor.kill_ring.borrow_mut().stop_killing();
gwenn's avatar
gwenn committed
            }
            Cmd::Kill(Movement::WholeLine) => {
gwenn's avatar
gwenn committed
                try!(edit_move_home(&mut s));
                editor.kill_ring.borrow_mut().start_killing();
gwenn's avatar
gwenn committed
                try!(edit_kill_line(&mut s));
                editor.kill_ring.borrow_mut().stop_killing();
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
            Cmd::ClearScreen => {
gwenn's avatar
gwenn committed
                // Clear the screen leaving the current line at the top of the screen.
gwenn's avatar
gwenn committed
                try!(s.out.clear_screen());
                try!(s.refresh_line())
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
            Cmd::NextHistory => {
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
            Cmd::PreviousHistory => {
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
            Cmd::HistorySearchBackward => try!(edit_history_search(
                &mut s,
                &editor.history,
                Direction::Reverse,
            )),
            Cmd::HistorySearchForward => try!(edit_history_search(
                &mut s,
                &editor.history,
                Direction::Forward,
            )),
gwenn's avatar
gwenn committed
            Cmd::TransposeChars => {
gwenn's avatar
gwenn committed
                // Exchange the char before cursor with the character at cursor.
                try!(edit_transpose_chars(&mut s))
            }
            Cmd::Kill(Movement::BeginningOfLine) => {
gwenn's avatar
gwenn committed
                // Kill backward from point to the beginning of the line.
                editor.kill_ring.borrow_mut().start_killing();
gwenn's avatar
gwenn committed
                try!(edit_discard_line(&mut s));
                editor.kill_ring.borrow_mut().stop_killing();
gwenn's avatar
gwenn committed
            }
gwenn's avatar
gwenn committed
            #[cfg(unix)]
gwenn's avatar
gwenn committed
            Cmd::QuotedInsert => {
                // Quoted insert
gwenn's avatar
gwenn committed
                let c = try!(rdr.next_char());
                try!(edit_insert(&mut s, c, 1)) // FIXME
gwenn's avatar
gwenn committed
            Cmd::Yank(n, anchor) => {
gwenn's avatar
gwenn committed
                // retrieve (yank) last item killed
gwenn's avatar
gwenn committed
                if let Some(text) = editor.kill_ring.borrow_mut().yank() {
gwenn's avatar
gwenn committed
                    try!(edit_yank(&mut s, text, anchor, n))
gwenn's avatar
gwenn committed
                }
            }
gwenn's avatar
gwenn committed
            Cmd::ViYankTo(mvt) => if let Some(text) = s.line.copy(mvt) {
                editor.kill_ring.borrow_mut().kill(&text, Mode::Append)
            },
gwenn's avatar
gwenn committed
            // TODO CTRL-_ // undo
gwenn's avatar
gwenn committed
            Cmd::AcceptLine => {
gwenn's avatar
gwenn committed
                // Accept the line regardless of where the cursor is.
                try!(edit_move_end(&mut s));
gwenn's avatar
gwenn committed
                if s.hinter.is_some() {
                    // Force a refresh without hints to leave the previous
                    // line as the user typed it after a newline.
                    s.hinter = None;
                    try!(s.refresh_line());
                }
gwenn's avatar
gwenn committed
                break;
            }