blob: baace4cacfd4daddb213ed2261bd51b598f6ff73 [file] [log] [blame]
//! Readline for Rust
//!
//! This implementation is based on [Antirez's
//! Linenoise](https://github.com/antirez/linenoise)
//!
//! # Example
//!
//! Usage
//!
//! ```
//! let mut rl = rustyline::Editor::<()>::new();
//! let readline = rl.readline(">> ");
//! match readline {
//! Ok(line) => println!("Line: {:?}", line),
//! Err(_) => println!("No input"),
//! }
//! ```
#![feature(io)]
#![feature(unicode)]
#![allow(unknown_lints)]
extern crate libc;
#[macro_use]
extern crate log;
#[cfg(unix)]
extern crate nix;
extern crate std_unicode;
extern crate unicode_segmentation;
extern crate unicode_width;
#[cfg(windows)]
extern crate winapi;
pub mod completion;
mod consts;
pub mod error;
pub mod hint;
pub mod history;
mod keymap;
mod kill_ring;
pub mod line_buffer;
pub mod config;
mod undo;
mod tty;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::io::{self, Write};
use std::path::Path;
use std::result;
use std::rc::Rc;
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use tty::{Position, RawMode, RawReader, Renderer, Term, Terminal};
use completion::{longest_common_prefix, Completer};
use hint::Hinter;
use history::{Direction, History};
use line_buffer::{LineBuffer, WordAction, MAX_LINE};
pub use keymap::{Anchor, At, CharSearch, Cmd, Movement, RepeatCount, Word};
use keymap::EditState;
use kill_ring::{KillRing, Mode};
pub use config::{CompletionType, Config, EditMode, HistoryDuplicates};
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>;
/// Represent the state during line editing.
/// Implement rendering.
struct State<'out, 'prompt> {
out: &'out mut Renderer,
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
saved_line_for_history: LineBuffer, // Current edited line before history browsing
byte_buffer: [u8; 4],
edit_state: EditState,
changes: Rc<RefCell<Changeset>>,
hinter: Option<&'out Hinter>,
}
impl<'out, 'prompt> State<'out, 'prompt> {
fn new(
out: &'out mut Renderer,
config: &Config,
prompt: &'prompt str,
history_index: usize,
custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>,
hinter: Option<&'out Hinter>,
) -> State<'out, 'prompt> {
let capacity = MAX_LINE;
let prompt_size = out.calculate_position(prompt, Position::default());
State {
out: out,
prompt: prompt,
prompt_size: prompt_size,
line: LineBuffer::with_capacity(capacity),
cursor: prompt_size,
old_rows: 0,
history_index: history_index,
saved_line_for_history: LineBuffer::with_capacity(capacity),
byte_buffer: [0; 4],
edit_state: EditState::new(config, custom_bindings),
changes: Rc::new(RefCell::new(Changeset::new())),
hinter: hinter,
}
}
fn next_cmd<R: RawReader>(&mut self, rdr: &mut R) -> Result<Cmd> {
loop {
let rc = self.edit_state.next_cmd(rdr);
if rc.is_err() && self.out.sigwinch() {
self.out.update_size();
try!(self.refresh_line());
continue;
}
return rc;
}
}
fn backup(&mut self) {
self.saved_line_for_history
.update(self.line.as_str(), self.line.pos());
}
fn restore(&mut self) {
self.line.update(
self.saved_line_for_history.as_str(),
self.saved_line_for_history.pos(),
);
}
fn move_cursor(&mut self) -> Result<()> {
// calculate the desired position of the cursor
let cursor = self.out
.calculate_position(&self.line[..self.line.pos()], self.prompt_size);
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<()> {
let prompt_size = self.prompt_size;
let hint = self.hint();
self.refresh(self.prompt, prompt_size, hint)
}
fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()> {
let prompt_size = self.out.calculate_position(prompt, Position::default());
let hint = self.hint();
self.refresh(prompt, prompt_size, hint)
}
fn refresh(&mut self, prompt: &str, prompt_size: Position, hint: Option<String>) -> Result<()> {
let (cursor, end_pos) = try!(self.out.refresh_line(
prompt,
prompt_size,
&self.line,
hint,
self.cursor.row,
self.old_rows,
));
self.cursor = cursor;
self.old_rows = end_pos.row;
Ok(())
}
fn hint(&self) -> Option<String> {
if let Some(hinter) = self.hinter {
hinter.hint(self.line.as_str(), self.line.pos())
} else {
None
}
}
}
impl<'out, 'prompt> fmt::Debug for State<'out, 'prompt> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("State")
.field("prompt", &self.prompt)
.field("prompt_size", &self.prompt_size)
.field("buf", &self.line)
.field("cursor", &self.cursor)
.field("cols", &self.out.get_columns())
.field("old_rows", &self.old_rows)
.field("history_index", &self.history_index)
.field("saved_line_for_history", &self.saved_line_for_history)
.finish()
}
}
/// Insert the character `ch` at cursor current position.
fn edit_insert(s: &mut State, ch: char, n: RepeatCount) -> Result<()> {
if let Some(push) = s.line.insert(ch, n) {
if push {
let prompt_size = s.prompt_size;
let hint = s.hint();
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.
let cursor = s.out
.calculate_position(&s.line[..s.line.pos()], s.prompt_size);
s.cursor = cursor;
let bits = ch.encode_utf8(&mut s.byte_buffer);
let bits = bits.as_bytes();
s.out.write_and_flush(bits)
} else {
s.refresh(s.prompt, prompt_size, hint)
}
} else {
s.refresh_line()
}
} else {
Ok(())
}
}
/// Replace a single (or n) character(s) under the cursor (Vi mode)
fn edit_replace_char(s: &mut State, ch: char, n: RepeatCount) -> Result<()> {
s.changes.borrow_mut().begin();
let succeed = if let Some(chars) = s.line.delete(n) {
let count = chars.graphemes(true).count();
s.line.insert(ch, count);
s.line.move_backward(1);
true
} else {
false
};
s.changes.borrow_mut().end();
if succeed {
s.refresh_line()
} else {
Ok(())
}
}
/// 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(())
}
}
// Yank/paste `text` at current position.
fn edit_yank(s: &mut State, text: &str, anchor: Anchor, n: RepeatCount) -> Result<()> {
if let Anchor::After = anchor {
s.line.move_forward(1);
}
if s.line.yank(text, n).is_some() {
if !s.edit_state.is_emacs_mode() {
s.line.move_backward(1);
}
s.refresh_line()
} else {
Ok(())
}
}
// Delete previously yanked text and yank/paste `text` at current position.
fn edit_yank_pop(s: &mut State, yank_size: usize, text: &str) -> Result<()> {
s.changes.borrow_mut().begin();
let result = if s.line.yank_pop(yank_size, text).is_some() {
s.refresh_line()
} else {
Ok(())
};
s.changes.borrow_mut().end();
result
}
/// Move cursor on the left.
fn edit_move_backward(s: &mut State, n: RepeatCount) -> Result<()> {
if s.line.move_backward(n) {
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) {
s.move_cursor()
} else {
Ok(())
}
}
/// Move cursor to the start of the line.
fn edit_move_home(s: &mut State) -> Result<()> {
if s.line.move_home() {
s.move_cursor()
} else {
Ok(())
}
}
/// Move cursor to the end of the line.
fn edit_move_end(s: &mut State) -> Result<()> {
if s.line.move_end() {
s.move_cursor()
} 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, n: RepeatCount) -> Result<()> {
if s.line.delete(n).is_some() {
s.refresh_line()
} else {
Ok(())
}
}
/// Backspace implementation.
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()
} else {
Ok(())
}
}
/// 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()
} else {
Ok(())
}
}
/// Exchange the char before cursor with the character at cursor.
fn edit_transpose_chars(s: &mut State) -> Result<()> {
s.changes.borrow_mut().begin();
let succeed = s.line.transpose_chars();
s.changes.borrow_mut().end();
if succeed {
s.refresh_line()
} else {
Ok(())
}
}
fn edit_move_to_prev_word(s: &mut State, word_def: Word, n: RepeatCount) -> Result<()> {
if s.line.move_to_prev_word(word_def, n) {
s.move_cursor()
} 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()
} else {
Ok(())
}
}
fn edit_move_to_next_word(s: &mut State, at: At, word_def: Word, n: RepeatCount) -> Result<()> {
if s.line.move_to_next_word(at, word_def, n) {
s.move_cursor()
} else {
Ok(())
}
}
fn edit_move_to(s: &mut State, cs: CharSearch, n: RepeatCount) -> Result<()> {
if s.line.move_to(cs, n) {
s.move_cursor()
} 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, at: At, word_def: Word, n: RepeatCount) -> Result<()> {
if s.line.delete_word(at, word_def, n) {
s.refresh_line()
} else {
Ok(())
}
}
fn edit_delete_to(s: &mut State, cs: CharSearch, n: RepeatCount) -> Result<()> {
if s.line.delete_to(cs, n) {
s.refresh_line()
} else {
Ok(())
}
}
fn edit_word(s: &mut State, a: WordAction) -> Result<()> {
s.changes.borrow_mut().begin();
let succeed = s.line.edit_word(a);
s.changes.borrow_mut().end();
if succeed {
s.refresh_line()
} else {
Ok(())
}
}
fn edit_transpose_words(s: &mut State, n: RepeatCount) -> Result<()> {
s.changes.borrow_mut().begin();
let succeed = s.line.transpose_words(n);
s.changes.borrow_mut().end();
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<()> {
if history.is_empty() {
return Ok(());
}
if s.history_index == history.len() {
if prev {
// Save the current edited line before overwriting it
s.backup();
} else {
return Ok(());
}
} else if s.history_index == 0 && prev {
return Ok(());
}
if prev {
s.history_index -= 1;
} else {
s.history_index += 1;
}
if s.history_index < history.len() {
let buf = history.get(s.history_index).unwrap();
s.changes.borrow_mut().begin();
s.line.update(buf, buf.len());
s.changes.borrow_mut().end();
} else {
// Restore current edited line
s.restore();
}
s.refresh_line()
}
// Non-incremental, anchored search
fn edit_history_search(s: &mut State, history: &History, dir: Direction) -> Result<()> {
if history.is_empty() {
return s.out.beep();
}
if s.history_index == history.len() && dir == Direction::Forward {
return s.out.beep();
} else if s.history_index == 0 && dir == Direction::Reverse {
return s.out.beep();
}
if dir == Direction::Reverse {
s.history_index -= 1;
} else {
s.history_index += 1;
}
if let Some(history_index) =
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();
s.changes.borrow_mut().begin();
s.line.update(buf, buf.len());
s.changes.borrow_mut().end();
s.refresh_line()
} else {
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 {
// 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();
s.changes.borrow_mut().begin();
s.line.update(buf, buf.len());
s.changes.borrow_mut().end();
} else {
s.history_index = history.len();
// Restore current edited line
s.restore();
}
s.refresh_line()
}
/// Completes the line/word
fn complete_line<R: RawReader, C: Completer>(
rdr: &mut R,
s: &mut State,
completer: &C,
config: &Config,
) -> Result<Option<Cmd>> {
// get a list of completions
let (start, candidates) = try!(completer.complete(&s.line, s.line.pos()));
// if no completions, we are done
if candidates.is_empty() {
try!(s.out.beep());
Ok(None)
} else if CompletionType::Circular == config.completion_type() {
let mark = s.changes.borrow_mut().begin();
// Save the current edited line before overwriting it
let backup = s.line.as_str().to_owned();
let backup_pos = s.line.pos();
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());
} else {
// Restore current edited line
s.line.update(&backup, backup_pos);
try!(s.refresh_line());
}
cmd = try!(s.next_cmd(rdr));
match cmd {
Cmd::Complete => {
i = (i + 1) % (candidates.len() + 1); // Circular
if i == candidates.len() {
try!(s.out.beep());
}
}
Cmd::Abort => {
// Re-show original buffer
if i < candidates.len() {
s.line.update(&backup, backup_pos);
try!(s.refresh_line());
}
s.changes.borrow_mut().truncate(mark);
return Ok(None);
}
_ => {
s.changes.borrow_mut().end();
break;
}
}
}
Ok(Some(cmd))
} else if CompletionType::List == config.completion_type() {
// beep if ambiguous
if candidates.len() > 1 {
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 {
completer.update(&mut s.line, start, lcp);
try!(s.refresh_line());
return Ok(None);
}
}
// we can't complete any further, wait for second tab
let mut cmd = try!(s.next_cmd(rdr));
// if any character other than tab, pass it to the main loop
if cmd != Cmd::Complete {
return Ok(Some(cmd));
}
// 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);
// we got a second tab, maybe show list of possible completions
let show_completions = if candidates.len() > config.completion_prompt_limit() {
let msg = format!("\nDisplay all {} possibilities? (y or n)", candidates.len());
try!(s.out.write_and_flush(msg.as_bytes()));
s.old_rows += 1;
while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y')
&& cmd != Cmd::SelfInsert(1, 'n')
&& cmd != Cmd::SelfInsert(1, 'N')
&& cmd != Cmd::Kill(Movement::BackwardChar(1))
{
cmd = try!(s.next_cmd(rdr));
}
match cmd {
Cmd::SelfInsert(1, 'y') | Cmd::SelfInsert(1, 'Y') => true,
_ => false,
}
} else {
true
};
if show_completions {
page_completions(rdr, s, &candidates)
} else {
try!(s.refresh_line());
Ok(None)
}
} else {
Ok(None)
}
}
fn page_completions<R: RawReader>(
rdr: &mut R,
s: &mut State,
candidates: &[String],
) -> Result<Option<Cmd>> {
use std::cmp;
let min_col_pad = 2;
let cols = s.out.get_columns();
let max_width = cmp::min(
cols,
candidates
.into_iter()
.map(|s| s.as_str().width())
.max()
.unwrap() + min_col_pad,
);
let num_cols = cols / max_width;
let mut pause_row = s.out.get_rows() - 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!(s.out.write_and_flush(b"\n--More--"));
let mut cmd = Cmd::Noop;
while cmd != Cmd::SelfInsert(1, 'y') && cmd != Cmd::SelfInsert(1, 'Y')
&& cmd != Cmd::SelfInsert(1, 'n')
&& cmd != Cmd::SelfInsert(1, 'N')
&& cmd != Cmd::SelfInsert(1, 'q')
&& cmd != Cmd::SelfInsert(1, 'Q')
&& cmd != Cmd::SelfInsert(1, ' ')
&& cmd != Cmd::Kill(Movement::BackwardChar(1))
&& cmd != Cmd::AcceptLine
{
cmd = try!(s.next_cmd(rdr));
}
match cmd {
Cmd::SelfInsert(1, 'y') | Cmd::SelfInsert(1, 'Y') | Cmd::SelfInsert(1, ' ') => {
pause_row += s.out.get_rows() - 1;
}
Cmd::AcceptLine => {
pause_row += 1;
}
_ => break,
}
try!(s.out.write_and_flush(b"\n"));
} else {
try!(s.out.write_and_flush(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 = candidate.as_str().width();
if ((col + 1) * num_rows) + row < candidates.len() {
for _ in width..max_width {
ab.push(' ');
}
}
}
}
try!(s.out.write_and_flush(ab.as_bytes()));
}
try!(s.out.write_and_flush(b"\n"));
try!(s.refresh_line());
Ok(None)
}
/// Incremental search
fn reverse_incremental_search<R: RawReader>(
rdr: &mut R,
s: &mut State,
history: &History,
) -> Result<Option<Cmd>> {
if history.is_empty() {
return Ok(None);
}
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;
let mut cmd;
// Display the reverse-i-search prompt and process chars
loop {
let prompt = if success {
format!("(reverse-i-search)`{}': ", search_buf)
} else {
format!("(failed reverse-i-search)`{}': ", search_buf)
};
try!(s.refresh_prompt_and_line(&prompt));
cmd = try!(s.next_cmd(rdr));
if let Cmd::SelfInsert(_, c) = cmd {
search_buf.push(c);
} else {
match cmd {
Cmd::Kill(Movement::BackwardChar(_)) => {
search_buf.pop();
continue;
}
Cmd::ReverseSearchHistory => {
direction = Direction::Reverse;
if history_idx > 0 {
history_idx -= 1;
} else {
success = false;
continue;
}
}
Cmd::ForwardSearchHistory => {
direction = Direction::Forward;
if history_idx < history.len() - 1 {
history_idx += 1;
} else {
success = false;
continue;
}
}
Cmd::Abort => {
// Restore current edited line (before search)
s.line.update(&backup, backup_pos);
try!(s.refresh_line());
s.changes.borrow_mut().truncate(mark);
return Ok(None);
}
_ => break,
}
}
success = match history.search(&search_buf, history_idx, direction) {
Some(idx) => {
history_idx = idx;
let entry = history.get(idx).unwrap();
let pos = entry.find(&search_buf).unwrap();
s.line.update(entry, pos);
true
}
_ => false,
};
}
s.changes.borrow_mut().end();
Ok(Some(cmd))
}
/// Handles reading and editting the readline buffer.
/// It will also handle special inputs in an appropriate fashion
/// (e.g., C-c will exit readline)
#[allow(let_unit_value)]
fn readline_edit<H: Helper>(
prompt: &str,
initial: Option<(&str, &str)>,
editor: &mut Editor<H>,
original_mode: &tty::Mode,
) -> Result<String> {
let completer = editor.helper.as_ref().map(|h| h.completer());
let hinter = editor.helper.as_ref().map(|h| h.hinter() as &Hinter);
let mut stdout = editor.term.create_writer();
editor.reset_kill_ring();
let mut s = State::new(
&mut stdout,
&editor.config,
prompt,
editor.history.len(),
Rc::clone(&editor.custom_bindings),
hinter,
);
s.line.set_delete_listener(editor.kill_ring.clone());
s.line.set_change_listener(s.changes.clone());
if let Some((left, right)) = initial {
s.line
.update((left.to_owned() + right).as_ref(), left.len());
}
try!(s.refresh_line());
let mut rdr = try!(editor.term.create_reader(&editor.config));
loop {
let rc = s.next_cmd(&mut rdr);
let mut cmd = try!(rc);
if cmd.should_reset_kill_ring() {
editor.reset_kill_ring();
}
// autocomplete
if cmd == Cmd::Complete && completer.is_some() {
let next = try!(complete_line(
&mut rdr,
&mut s,
completer.unwrap(),
&editor.config,
));
if next.is_some() {
cmd = next.unwrap();
} else {
continue;
}
}
if let Cmd::SelfInsert(n, c) = cmd {
try!(edit_insert(&mut s, c, n));
continue;
} else if let Cmd::Insert(n, text) = cmd {
try!(edit_yank(&mut s, &text, Anchor::Before, n));
continue;
}
if cmd == Cmd::ReverseSearchHistory {
// Search history backward
let next = try!(reverse_incremental_search(
&mut rdr,
&mut s,
&editor.history,
));
if next.is_some() {
cmd = next.unwrap();
} else {
continue;
}
}
match cmd {
Cmd::Move(Movement::BeginningOfLine) => {
// Move to the beginning of line.
try!(edit_move_home(&mut s))
}
Cmd::Move(Movement::ViFirstPrint) => {
try!(edit_move_home(&mut s));
try!(edit_move_to_next_word(&mut s, At::Start, Word::Big, 1))
}
Cmd::Move(Movement::BackwardChar(n)) => {
// Move back a character.
try!(edit_move_backward(&mut s, n))
}
Cmd::Kill(Movement::ForwardChar(n)) => {
// Delete (forward) one character at point.
try!(edit_delete(&mut s, n))
}
Cmd::Replace(n, c) => {
try!(edit_replace_char(&mut s, c, n));
}
Cmd::Overwrite(c) => {
try!(edit_overwrite_char(&mut s, c));
}
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))
},
Cmd::Move(Movement::EndOfLine) => {
// Move to the end of line.
try!(edit_move_end(&mut s))
}
Cmd::Move(Movement::ForwardChar(n)) => {
// Move forward a character.
try!(edit_move_forward(&mut s, n))
}
Cmd::Kill(Movement::BackwardChar(n)) => {
// Delete one character backward.
try!(edit_backspace(&mut s, n))
}
Cmd::Kill(Movement::EndOfLine) => {
// Kill the text from point to the end of the line.
editor.kill_ring.borrow_mut().start_killing();
try!(edit_kill_line(&mut s));
editor.kill_ring.borrow_mut().stop_killing();
}
Cmd::Kill(Movement::WholeLine) => {
try!(edit_move_home(&mut s));
editor.kill_ring.borrow_mut().start_killing();
try!(edit_kill_line(&mut s));
editor.kill_ring.borrow_mut().stop_killing();
}
Cmd::ClearScreen => {
// Clear the screen leaving the current line at the top of the screen.
try!(s.out.clear_screen());
try!(s.refresh_line())
}
Cmd::NextHistory => {
// Fetch the next command from the history list.
try!(edit_history_next(&mut s, &editor.history, false))
}
Cmd::PreviousHistory => {
// Fetch the previous command from the history list.
try!(edit_history_next(&mut s, &editor.history, true))
}
Cmd::HistorySearchBackward => try!(edit_history_search(
&mut s,
&editor.history,
Direction::Reverse,
)),
Cmd::HistorySearchForward => try!(edit_history_search(
&mut s,
&editor.history,
Direction::Forward,
)),
Cmd::TransposeChars => {
// Exchange the char before cursor with the character at cursor.
try!(edit_transpose_chars(&mut s))
}
Cmd::Kill(Movement::BeginningOfLine) => {
// Kill backward from point to the beginning of the line.
editor.kill_ring.borrow_mut().start_killing();
try!(edit_discard_line(&mut s));
editor.kill_ring.borrow_mut().stop_killing();
}
#[cfg(unix)]
Cmd::QuotedInsert => {
// Quoted insert
let c = try!(rdr.next_char());
try!(edit_insert(&mut s, c, 1)) // FIXME
}
Cmd::Yank(n, anchor) => {
// retrieve (yank) last item killed
if let Some(text) = editor.kill_ring.borrow_mut().yank() {
try!(edit_yank(&mut s, text, anchor, n))
}
}
Cmd::ViYankTo(mvt) => if let Some(text) = s.line.copy(mvt) {
editor.kill_ring.borrow_mut().kill(&text, Mode::Append)
},
// TODO CTRL-_ // undo
Cmd::AcceptLine => {
// Accept the line regardless of where the cursor is.
try!(edit_move_end(&mut s));
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());
}
break;
}
Cmd::Kill(Movement::BackwardWord(n, word_def)) => {
// kill one word backward (until start of word)
editor.kill_ring.borrow_mut().start_killing();
try!(edit_delete_prev_word(&mut s, word_def, n));
editor.kill_ring.borrow_mut().stop_killing();
}
Cmd::BeginningOfHistory => {
// move to first entry in history
try!(edit_history(&mut s, &editor.history, true))
}
Cmd::EndOfHistory => {
// move to last entry in history
try!(edit_history(&mut s, &editor.history, false))
}
Cmd::Move(Movement::BackwardWord(n, word_def)) => {
// move backwards one word
try!(edit_move_to_prev_word(&mut s, word_def, n))
}
Cmd::CapitalizeWord => {
// capitalize word after point
try!(edit_word(&mut s, WordAction::CAPITALIZE))
}
Cmd::Kill(Movement::ForwardWord(n, at, word_def)) => {
// kill one word forward (until start/end of word)
editor.kill_ring.borrow_mut().start_killing();
try!(edit_delete_word(&mut s, at, word_def, n));
editor.kill_ring.borrow_mut().stop_killing();
}
Cmd::Move(Movement::ForwardWord(n, at, word_def)) => {
// move forwards one word
try!(edit_move_to_next_word(&mut s, at, word_def, n))
}
Cmd::DowncaseWord => {
// lowercase word after point
try!(edit_word(&mut s, WordAction::LOWERCASE))
}
Cmd::TransposeWords(n) => {
// transpose words
try!(edit_transpose_words(&mut s, n))
}
Cmd::UpcaseWord => {
// uppercase word after point
try!(edit_word(&mut s, WordAction::UPPERCASE))
}
Cmd::YankPop => {
// yank-pop
if let Some((yank_size, text)) = editor.kill_ring.borrow_mut().yank_pop() {
try!(edit_yank_pop(&mut s, yank_size, text))
}
}
Cmd::Move(Movement::ViCharSearch(n, cs)) => try!(edit_move_to(&mut s, cs, n)),
Cmd::Kill(Movement::ViCharSearch(n, cs)) => {
editor.kill_ring.borrow_mut().start_killing();
try!(edit_delete_to(&mut s, cs, n));
editor.kill_ring.borrow_mut().stop_killing();
}
Cmd::Undo => {
s.line.remove_change_listener();
if s.changes.borrow_mut().undo(&mut s.line) {
try!(s.refresh_line());
}
s.line.set_change_listener(s.changes.clone());
}
Cmd::Interrupt => {
return Err(error::ReadlineError::Interrupted);
}
#[cfg(unix)]
Cmd::Suspend => {
try!(original_mode.disable_raw_mode());
try!(tty::suspend());
try!(editor.term.enable_raw_mode()); // TODO original_mode may have changed
try!(s.refresh_line());
continue;
}
Cmd::Noop => {}
_ => {
// Ignore the character typed.
}
}
}
Ok(s.line.into_string())
}
struct Guard<'m>(&'m tty::Mode);
#[allow(unused_must_use)]
impl<'m> Drop for Guard<'m> {
fn drop(&mut self) {
let Guard(mode) = *self;
mode.disable_raw_mode();
}
}
/// Readline method that will enable RAW mode, call the `readline_edit()`
/// method and disable raw mode
fn readline_raw<H: Helper>(
prompt: &str,
initial: Option<(&str, &str)>,
editor: &mut Editor<H>,
) -> Result<String> {
let original_mode = try!(editor.term.enable_raw_mode());
let guard = Guard(&original_mode);
let user_input = readline_edit(prompt, initial, editor, &original_mode);
if editor.config.auto_add_history() {
if let Ok(ref line) = user_input {
editor.add_history_entry(line.as_ref());
}
}
drop(guard); // try!(disable_raw_mode(original_mode));
println!();
user_input
}
fn readline_direct() -> Result<String> {
let mut line = String::new();
if try!(io::stdin().read_line(&mut line)) > 0 {
Ok(line)
} else {
Err(error::ReadlineError::Eof)
}
}
pub trait Helper {
type Completer: Completer;
type Hinter: Hinter;
fn completer(&self) -> &Self::Completer;
fn hinter(&self) -> &Self::Hinter;
}
impl<C: Completer, H: Hinter> Helper for (C, H) {
type Completer = C;
type Hinter = H;
fn completer(&self) -> &C {
&self.0
}
fn hinter(&self) -> &H {
&self.1
}
}
impl<C: Completer> Helper for C {
type Completer = C;
type Hinter = ();
fn completer(&self) -> &C {
self
}
fn hinter(&self) -> &() {
&()
}
}
/// Line editor
pub struct Editor<H: Helper> {
term: Terminal,
history: History,
helper: Option<H>,
kill_ring: Rc<RefCell<KillRing>>,
config: Config,
custom_bindings: Rc<RefCell<HashMap<KeyPress, Cmd>>>,
}
impl<H: Helper> Editor<H> {
/// Create an editor with the default configuration
pub fn new() -> Editor<H> {
Self::with_config(Config::default())
}
/// Create an editor with a specific configuration.
pub fn with_config(config: Config) -> Editor<H> {
let term = Terminal::new();
Editor {
term: term,
history: History::with_config(config),
helper: None,
kill_ring: Rc::new(RefCell::new(KillRing::new(60))),
config: config,
custom_bindings: Rc::new(RefCell::new(HashMap::new())),
}
}
/// This method will read a line from STDIN and will display a `prompt`.
///
/// It uses terminal-style interaction if `stdin` is connected to a
/// terminal.
/// Otherwise (e.g., if `stdin` is a pipe or the terminal is not supported),
/// it uses file-style interaction.
pub fn readline(&mut self, prompt: &str) -> Result<String> {
self.readline_with(prompt, None)
}
/// This function behaves in the exact same manner as `readline`, except
/// that it pre-populates the input area.
///
/// The text that resides in the input area is given as a 2-tuple.
/// The string on the left of the tuple is what will appear to the left of the cursor
/// and the string on the right is what will appear to the right of the cursor.
pub fn readline_with_initial(&mut self, prompt: &str, initial: (&str, &str)) -> Result<String> {
self.readline_with(prompt, Some(initial))
}
fn readline_with(&mut self, prompt: &str, initial: Option<(&str, &str)>) -> Result<String> {
if self.term.is_unsupported() {
debug!(target: "rustyline", "unsupported terminal");
// Write prompt and flush it to stdout
let mut stdout = io::stdout();
try!(stdout.write_all(prompt.as_bytes()));
try!(stdout.flush());
readline_direct()
} else if !self.term.is_stdin_tty() {
debug!(target: "rustyline", "stdin is not a tty");
// Not a tty: read from file / pipe.
readline_direct()
} else {
readline_raw(prompt, initial, self)
}
}
/// Load the history from the specified file.
pub fn load_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> Result<()> {
self.history.load(path)
}
/// Save the history in the specified file.
pub fn save_history<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<()> {
self.history.save(path)
}
/// Add a new entry in the history.
pub fn add_history_entry<S: AsRef<str> + Into<String>>(&mut self, line: S) -> bool {
self.history.add(line)
}
/// Clear history.
pub fn clear_history(&mut self) {
self.history.clear()
}
/// Return a mutable reference to the history object.
pub fn get_history(&mut self) -> &mut History {
&mut self.history
}
/// Return an immutable reference to the history object.
pub fn get_history_const(&self) -> &History {
&self.history
}
/// Register a callback function to be called for tab-completion
/// or to show hints to the user at the right of the prompt.
pub fn set_helper(&mut self, helper: Option<H>) {
self.helper = helper;
}
/// Bind a sequence to a command.
pub fn bind_sequence(&mut self, key_seq: KeyPress, cmd: Cmd) -> Option<Cmd> {
self.custom_bindings.borrow_mut().insert(key_seq, cmd)
}
/// Remove a binding for the given sequence.
pub fn unbind_sequence(&mut self, key_seq: KeyPress) -> Option<Cmd> {
self.custom_bindings.borrow_mut().remove(&key_seq)
}
/// ```
/// let mut rl = rustyline::Editor::<()>::new();
/// for readline in rl.iter("> ") {
/// match readline {
/// Ok(line) => {
/// println!("Line: {}", line);
/// }
/// Err(err) => {
/// println!("Error: {:?}", err);
/// break;
/// }
/// }
/// }
/// ```
pub fn iter<'a>(&'a mut self, prompt: &'a str) -> Iter<H> {
Iter {
editor: self,
prompt: prompt,
}
}
fn reset_kill_ring(&self) {
self.kill_ring.borrow_mut().reset();
}
}
impl<H: Helper> fmt::Debug for Editor<H> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Editor")
.field("term", &self.term)
.field("config", &self.config)
.finish()
}
}
/// Edited lines iterator
pub struct Iter<'a, H: Helper>
where
H: 'a,
{
editor: &'a mut Editor<H>,
prompt: &'a str,
}
impl<'a, H: Helper> Iterator for Iter<'a, H> {
type Item = Result<String>;
fn next(&mut self) -> Option<Result<String>> {
let readline = self.editor.readline(self.prompt);
match readline {
Ok(l) => Some(Ok(l)),
Err(error::ReadlineError::Eof) => None,
e @ Err(_) => Some(e),
}
}
}
#[cfg(test)]
mod test {
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use line_buffer::LineBuffer;
use history::History;
use completion::Completer;
use config::Config;
use consts::KeyPress;
use keymap::{Cmd, EditState};
use super::{Editor, Position, Result, State};
use tty::Renderer;
use undo::Changeset;
fn init_state<'out>(out: &'out mut Renderer, line: &str, pos: usize) -> State<'out, 'static> {
let config = Config::default();
State {
out: out,
prompt: "",
prompt_size: Position::default(),
line: LineBuffer::init(line, pos, None),
cursor: Position::default(),
old_rows: 0,
history_index: 0,
saved_line_for_history: LineBuffer::with_capacity(100),
byte_buffer: [0; 4],
edit_state: EditState::new(&config, Rc::new(RefCell::new(HashMap::new()))),
changes: Rc::new(RefCell::new(Changeset::new())),
hinter: None,
}
}
fn init_editor(keys: &[KeyPress]) -> Editor<()> {
let mut editor = Editor::<()>::new();
editor.term.keys.extend(keys.iter().cloned());
editor
}
#[test]
fn edit_history_next() {
let mut out = ::std::io::sink();
let line = "current edited line";
let mut s = init_state(&mut out, line, 6);
let mut history = History::new();
history.add("line0");
history.add("line1");
s.history_index = history.len();
for _ in 0..2 {
super::edit_history_next(&mut s, &history, false).unwrap();
assert_eq!(line, s.line.as_str());
}
super::edit_history_next(&mut s, &history, true).unwrap();
assert_eq!(line, s.saved_line_for_history.as_str());
assert_eq!(1, s.history_index);
assert_eq!("line1", s.line.as_str());
for _ in 0..2 {
super::edit_history_next(&mut s, &history, true).unwrap();
assert_eq!(line, s.saved_line_for_history.as_str());
assert_eq!(0, s.history_index);
assert_eq!("line0", s.line.as_str());
}
super::edit_history_next(&mut s, &history, false).unwrap();
assert_eq!(line, s.saved_line_for_history.as_str());
assert_eq!(1, s.history_index);
assert_eq!("line1", s.line.as_str());
super::edit_history_next(&mut s, &history, false).unwrap();
// assert_eq!(line, s.saved_line_for_history);
assert_eq!(2, s.history_index);
assert_eq!(line, s.line.as_str());
}
struct SimpleCompleter;
impl Completer for SimpleCompleter {
fn complete(&self, line: &str, _pos: usize) -> Result<(usize, Vec<String>)> {
Ok((0, vec![line.to_owned() + "t"]))
}
}
#[test]
fn complete_line() {
let mut out = ::std::io::sink();
let mut s = init_state(&mut out, "rus", 3);
let keys = &[KeyPress::Enter];
let mut rdr = keys.iter();
let completer = SimpleCompleter;
let cmd = super::complete_line(&mut rdr, &mut s, &completer, &Config::default()).unwrap();
assert_eq!(Some(Cmd::AcceptLine), cmd);
assert_eq!("rust", s.line.as_str());
assert_eq!(4, s.line.pos());
}
fn assert_line(keys: &[KeyPress], expected_line: &str) {
let mut editor = init_editor(keys);
let actual_line = editor.readline(&">>").unwrap();
assert_eq!(expected_line, actual_line);
}
#[test]
fn delete_key() {
assert_line(
&[KeyPress::Char('a'), KeyPress::Delete, KeyPress::Enter],
"a",
);
assert_line(
&[
KeyPress::Char('a'),
KeyPress::Left,
KeyPress::Delete,
KeyPress::Enter,
],
"",
);
}
#[test]
fn down_key() {
assert_line(&[KeyPress::Down, KeyPress::Enter], "");
}
#[test]
fn end_key() {
assert_line(&[KeyPress::End, KeyPress::Enter], "");
}
#[test]
fn home_key() {
assert_line(&[KeyPress::Home, KeyPress::Enter], "");
}
#[test]
fn left_key() {
assert_line(&[KeyPress::Left, KeyPress::Enter], "");
}
#[test]
fn meta_backspace_key() {
assert_line(&[KeyPress::Meta('\x08'), KeyPress::Enter], "");
}
#[test]
fn page_down_key() {
assert_line(&[KeyPress::PageDown, KeyPress::Enter], "");
}
#[test]
fn page_up_key() {
assert_line(&[KeyPress::PageUp, KeyPress::Enter], "");
}
#[test]
fn right_key() {
assert_line(&[KeyPress::Right, KeyPress::Enter], "");
}
#[test]
fn up_key() {
assert_line(&[KeyPress::Up, KeyPress::Enter], "");
}
#[test]
fn unknown_esc_key() {
assert_line(&[KeyPress::UnknownEscSeq, KeyPress::Enter], "");
}
}