blob: d00c6554c4adf7bcce71ca4941b851d9507265a4 [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(drain)]
#![feature(io)]
#![feature(str_char)]
#![feature(unicode)]
extern crate libc;
extern crate nix;
extern crate unicode_width;
pub mod completion;
#[allow(non_camel_case_types)]
mod consts;
pub mod error;
pub mod history;
use std::fmt;
use std::io;
use std::io::{Read, Write};
use std::path::Path;
use std::result;
use nix::errno::Errno;
use nix::sys::termios;
use completion::Completer;
use consts::{KeyPress, char_to_key_press};
use history::History;
/// 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
prompt_width: usize, // Prompt Unicode width
buf: String, // Edited line buffer
pos: usize, // Current cursor position (byte position)
cols: usize, // Number of columns in terminal
history_index: usize, // The history index we are currently editing.
history_end: String, // Current edited line before history browsing
bytes: [u8; 4],
}
impl<'out, 'prompt> State<'out, 'prompt> {
fn new(out: &'out mut Write, prompt: &'prompt str, capacity: usize, cols: usize, history_index: usize) -> State<'out, 'prompt> {
State {
out: out,
prompt: prompt,
prompt_width: unicode_width::UnicodeWidthStr::width(prompt),
buf: String::with_capacity(capacity),
pos: 0,
cols: cols,
history_index: history_index,
history_end: String::new(),
bytes: [0; 4],
}
}
fn update_buf(&mut self, buf: &str) {
self.buf = String::from(buf);
if self.buf.capacity() < MAX_LINE {
let cap = self.buf.capacity();
self.buf.reserve_exact(MAX_LINE - cap);
}
}
}
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_width", &self.prompt_width)
.field("buf", &self.buf)
.field("buf length", &self.buf.len())
.field("buf capacity", &self.buf.capacity())
.field("pos", &self.pos)
.field("cols", &self.cols)
.field("history_index", &self.history_index)
.field("history_end", &self.history_end)
.finish()
}
}
/// Maximum buffer size for the line read
static MAX_LINE: usize = 4096;
/// Unsupported Terminals that don't support RAW mode
static UNSUPPORTED_TERM: [&'static str; 3] = ["dumb","cons25","emacs"];
/// Check to see if STDIN is a TTY
fn is_a_tty() -> bool {
let isatty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0;
isatty
}
/// Check to see if the current `TERM` is unsupported
fn is_unsupported_term() -> bool {
use std::ascii::AsciiExt;
match std::env::var("TERM") {
Ok(term) => {
let mut unsupported = false;
for iter in &UNSUPPORTED_TERM {
unsupported = (*iter).eq_ignore_ascii_case(&term)
}
unsupported
}
Err(_) => false
}
}
fn from_errno(errno: Errno) -> error::ReadlineError {
error::ReadlineError::from(nix::Error::from_errno(errno))
}
/// Enable raw mode for the TERM
fn enable_raw_mode() -> Result<termios::Termios> {
use nix::sys::termios::{BRKINT, ICRNL, INPCK, ISTRIP, IXON, OPOST, CS8, ECHO, ICANON, IEXTEN, ISIG, VMIN, VTIME};
if !is_a_tty() {
Err(from_errno(Errno::ENOTTY))
} else {
let original_term = try!(termios::tcgetattr(libc::STDIN_FILENO));
let mut raw = original_term;
raw.c_iflag = raw.c_iflag & !(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag = raw.c_oflag & !(OPOST);
raw.c_cflag = raw.c_cflag | (CS8);
raw.c_lflag = raw.c_lflag & !(ECHO | ICANON | IEXTEN | ISIG);
raw.c_cc[VMIN] = 1;
raw.c_cc[VTIME] = 0;
try!(termios::tcsetattr(libc::STDIN_FILENO, termios::TCSAFLUSH, &raw));
Ok(original_term)
}
}
/// Disable Raw mode for the term
fn disable_raw_mode(original_termios: termios::Termios) -> Result<()> {
try!(termios::tcsetattr(libc::STDIN_FILENO,
termios::TCSAFLUSH,
&original_termios));
Ok(())
}
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
const TIOCGWINSZ: libc::c_ulong = 0x40087468;
#[cfg(any(target_os = "linux", target_os = "android"))]
const TIOCGWINSZ: libc::c_ulong = 0x5413;
/// Try to get the number of columns in the current terminal,
/// or assume 80 if it fails.
#[cfg(any(target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "freebsd"))]
fn get_columns() -> usize {
use std::mem::zeroed;
use libc::c_ushort;
use nix::sys::ioctl;
unsafe {
#[repr(C)]
struct winsize {
ws_row: c_ushort,
ws_col: c_ushort,
ws_xpixel: c_ushort,
ws_ypixel: c_ushort
}
let mut size: winsize = zeroed();
match ioctl::read_into(libc::STDOUT_FILENO, TIOCGWINSZ, &mut size) {
Ok(_) => size.ws_col as usize, // TODO getCursorPosition
Err(_) => 80,
}
}
}
fn write_and_flush(w: &mut Write, buf: &[u8]) -> Result<()> {
try!(w.write_all(buf));
try!(w.flush());
Ok(())
}
/// Clear the screen. Used to handle ctrl+l
pub fn clear_screen(out: &mut Write) -> Result<()> {
write_and_flush(out, b"\x1b[H\x1b[2J")
}
/// 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")
}
// Control characters are treated as having zero width.
fn width(s: &str) -> usize {
unicode_width::UnicodeWidthStr::width(s)
}
/// Rewrite the currently edited line accordingly to the buffer content,
/// cursor position, and number of columns of the terminal.
fn refresh_line(s: &mut State) -> Result<()> {
use std::fmt::Write;
use unicode_width::UnicodeWidthChar;
let buf = &s.buf;
let mut start = 0;
let mut w1 = width(&buf[start..s.pos]);
// horizontal-scroll-mode
while s.prompt_width + w1 >= s.cols {
let ch = buf.char_at(start);
start += ch.len_utf8();
w1 -= UnicodeWidthChar::width(ch).unwrap_or(0);
}
let mut end = buf.len();
let mut w2 = width(&buf[start..end]);
while s.prompt_width + w2 > s.cols {
let ch = buf.char_at_reverse(end);
end -= ch.len_utf8();
w2 -= UnicodeWidthChar::width(ch).unwrap_or(0);
}
let mut ab = String::new();
// Cursor to left edge
ab.push('\r');
// Write the prompt and the current buffer content
ab.push_str(s.prompt);
ab.push_str(&s.buf[start..end]);
// Erase to right
ab.push_str("\x1b[0K");
// Move cursor to original position.
ab.write_fmt(format_args!("\r\x1b[{}C", w1 + s.prompt_width)).unwrap();
write_and_flush(s.out, ab.as_bytes())
}
/// Insert the character `ch` at cursor current position.
fn edit_insert(s: &mut State, ch: char) -> Result<()> {
if s.buf.len() < s.buf.capacity() {
if s.buf.len() == s.pos {
s.buf.push(ch);
let size = ch.encode_utf8(&mut s.bytes).unwrap();
s.pos += size;
if s.prompt_width + width(&s.buf) < s.cols {
// Avoid a full update of the line in the trivial case.
write_and_flush(s.out, &mut s.bytes[0..size])
} else {
refresh_line(s)
}
} else {
s.buf.insert(s.pos, ch);
s.pos += ch.len_utf8();
refresh_line(s)
}
} else {
Ok(())
}
}
/// Move cursor on the left.
fn edit_move_left(s: &mut State) -> Result<()> {
if s.pos > 0 {
let ch = s.buf.char_at_reverse(s.pos);
s.pos -= ch.len_utf8();
refresh_line(s)
} else {
Ok(())
}
}
/// Move cursor on the right.
fn edit_move_right(s: &mut State) -> Result<()> {
if s.pos != s.buf.len() {
let ch = s.buf.char_at(s.pos);
s.pos += ch.len_utf8();
refresh_line(s)
} else {
Ok(())
}
}
/// Move cursor to the start of the line.
fn edit_move_home(s: &mut State) -> Result<()> {
if s.pos > 0 {
s.pos = 0;
refresh_line(s)
} else {
Ok(())
}
}
/// Move cursor to the end of the line.
fn edit_move_end(s: &mut State) -> Result<()> {
if s.pos != s.buf.len() {
s.pos = s.buf.len();
refresh_line(s)
} 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<()> {
if s.buf.len() > 0 && s.pos < s.buf.len() {
s.buf.remove(s.pos);
refresh_line(s)
} else {
Ok(())
}
}
/// Backspace implementation.
fn edit_backspace(s: &mut State) -> Result<()> {
if s.pos > 0 && s.buf.len() > 0 {
let ch = s.buf.char_at_reverse(s.pos);
s.pos -= ch.len_utf8();
s.buf.remove(s.pos);
refresh_line(s)
} else {
Ok(())
}
}
/// Kill the text from point to the end of the line.
fn edit_kill_line(s: &mut State) -> Result<()> {
if s.buf.len() > 0 && s.pos < s.buf.len() {
s.buf.drain(s.pos..);
refresh_line(s)
} else {
Ok(())
}
}
/// Kill backward from point to the beginning of the line.
fn edit_discard_line(s: &mut State) -> Result<()> {
if s.pos > 0 && s.buf.len() > 0 {
s.buf.drain(..s.pos);
s.pos = 0;
refresh_line(s)
} else {
Ok(())
}
}
/// Exchange the char before cursor with the character at cursor.
fn edit_transpose_chars(s: &mut State) -> Result<()> {
if s.pos > 0 && s.pos < s.buf.len() {
let ch = s.buf.remove(s.pos);
let size = ch.len_utf8();
let och = s.buf.char_at_reverse(s.pos);
let osize = och.len_utf8();
s.buf.insert(s.pos - osize, ch);
if s.pos != s.buf.len()-size {
s.pos += size;
} else {
if size >= osize {
s.pos += size - osize;
} else {
s.pos -= osize - size;
}
}
refresh_line(s)
} else {
Ok(())
}
}
/// Delete the previous word, maintaining the cursor at the start of the
/// current word.
fn edit_delete_prev_word(s: &mut State) -> Result<()> {
if s.pos > 0 {
let old_pos = s.pos;
let mut ch = s.buf.char_at_reverse(s.pos);
while s.pos > 0 && ch.is_whitespace() {
s.pos -= ch.len_utf8();
ch = s.buf.char_at_reverse(s.pos);
}
while s.pos > 0 && !ch.is_whitespace() {
s.pos -= ch.len_utf8();
ch = s.buf.char_at_reverse(s.pos);
}
s.buf.drain(s.pos..old_pos);
refresh_line(s)
} else {
Ok(())
}
}
/// Substitute the currently edited line with the next or previous history
/// entry.
fn edit_history_next(s: &mut State, history: &mut History, prev: bool) -> Result<()> {
if history.len() > 0 {
if s.history_index == history.len() {
if prev {
// Save the current edited line before to overwrite it
s.history_end = s.buf.clone();
} 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.update_buf(buf);
} else {
let buf = s.history_end.clone(); // TODO how to avoid cloning?
s.update_buf(&buf);
};
s.pos = s.buf.len();
refresh_line(s)
} else {
Ok(())
}
}
/// Completes the line/word
fn complete_line<R: io::Read>(chars: &mut io::Chars<R>, s: &mut State, completer: &Completer) -> Result<Option<char>> {
let candidates = completer.complete(&s.buf, s.pos);
if candidates.is_empty() {
try!(beep());
Ok(None)
} else {
let mut ch;
let mut i = 0;
loop {
// Show completion or original buffer
if i < candidates.len() {
let buf = s.buf.clone(); // TODO how to avoid cloning?
let pos = s.pos;
let (tmp_buf, tmp_pos) = completer.update(&s.buf, s.pos, &candidates[i]);
s.buf = tmp_buf;
s.pos = tmp_pos;
try!(refresh_line(s));
s.update_buf(&buf);
s.pos = pos;
} else {
try!(refresh_line(s));
}
ch = try!(chars.next().unwrap());
let key = char_to_key_press(ch);
match key {
KeyPress::TAB => {
i = (i+1) % (candidates.len()+1); // Circular
if i == candidates.len() {
try!(beep());
}
},
KeyPress::ESC => { // Re-show original buffer
if i < candidates.len() {
try!(refresh_line(s));
}
break
},
_ => { // Update buffer and return
if i < candidates.len() {
let (buf, pos) = completer.update(&s.buf, s.pos, &candidates[i]);
s.update_buf(&buf);
s.pos = pos;
}
break
}
}
}
Ok(Some(ch))
}
}
/// Handles reading and editting the readline buffer.
/// It will also handle special inputs in an appropriate fashion
/// (e.g., C-c will exit readline)
fn readline_edit(prompt: &str, history: &mut History, completer: Option<&Completer>) -> Result<String> {
let mut stdout = io::stdout();
try!(write_and_flush(&mut stdout, prompt.as_bytes()));
let mut s = State::new(&mut stdout, prompt, MAX_LINE, get_columns(), history.len());
let stdin = io::stdin();
let mut chars = stdin.lock().chars();
loop {
let ch = try!(chars.next().unwrap());
let mut key = char_to_key_press(ch);
// autocomplete
if key == KeyPress::TAB && completer.is_some() {
let next = try!(complete_line(&mut chars, &mut s, completer.unwrap()));
if next.is_some() {
key = char_to_key_press(next.unwrap());
} else {
continue;
}
}
match key {
KeyPress::CTRL_A => try!(edit_move_home(&mut s)), // Move to the beginning of line.
KeyPress::CTRL_B => try!(edit_move_left(&mut s)), // Move back a character.
KeyPress::CTRL_C => {
return Err(error::ReadlineError::Interrupted)
},
KeyPress::CTRL_D => {
if s.buf.len() > 0 { // Delete (forward) one character at point.
try!(edit_delete(&mut s))
} else {
return Err(error::ReadlineError::Eof)
}
},
KeyPress::CTRL_E => try!(edit_move_end(&mut s)), // Move to the end of line.
KeyPress::CTRL_F => try!(edit_move_right(&mut s)), // Move forward a character.
KeyPress::CTRL_H | KeyPress::BACKSPACE => try!(edit_backspace(&mut s)), // Delete one character backward.
KeyPress::CTRL_K => try!(edit_kill_line(&mut s)), // Kill the text from point to the end of the line.
KeyPress::CTRL_L => { // Clear the screen leaving the current line at the top of the screen.
try!(clear_screen(s.out));
try!(refresh_line(&mut s))
},
KeyPress::CTRL_N => { // Fetch the next command from the history list.
try!(edit_history_next(&mut s, history, false))
},
KeyPress::CTRL_P => { // Fetch the previous command from the history list.
try!(edit_history_next(&mut s, history, true))
},
KeyPress::CTRL_T => try!(edit_transpose_chars(&mut s)), // Exchange the char before cursor with the character at cursor.
KeyPress::CTRL_U => try!(edit_discard_line(&mut s)), // Kill backward from point to the beginning of the line.
KeyPress::CTRL_W => try!(edit_delete_prev_word(&mut s)), // Kill the word behind point, using white space as a word boundary
KeyPress::ESC => { // escape sequence
// Read the next two bytes representing the escape sequence.
let seq1 = try!(chars.next().unwrap());
if seq1 == '[' { // ESC [ sequences.
let seq2 = try!(chars.next().unwrap());
if seq2.is_digit(10) { // Extended escape, read additional byte.
let seq3 = try!(chars.next().unwrap());
if seq3 == '~' {
match seq2 {
'3' => try!(edit_delete(&mut s)),
_ => (),
}
}
} else {
match seq2 {
'A' => try!(edit_history_next(&mut s, history, true)), // Up
'B' => try!(edit_history_next(&mut s, history, false)), // Down
'C' => try!(edit_move_right(&mut s)), // Right
'D' => try!(edit_move_left(&mut s)), // Left
'H' => try!(edit_move_home(&mut s)), // Home
'F' => try!(edit_move_end(&mut s)), // End
_ => ()
}
}
} else if seq1 == 'O' { // ESC O sequences.
let seq2 = try!(chars.next().unwrap());
match seq2 {
'H' => try!(edit_move_home(&mut s)),
'F' => try!(edit_move_end(&mut s)),
_ => ()
}
}
},
KeyPress::ENTER => break, // Accept the line regardless of where the cursor is.
_ => try!(edit_insert(&mut s, ch)), // Insert the character typed.
}
}
Ok(s.buf)
}
/// Readline method that will enable RAW mode, call the ```readline_edit()```
/// method and disable raw mode
fn readline_raw(prompt: &str, history: &mut History, completer: Option<&Completer>) -> Result<String> {
let original_termios = try!(enable_raw_mode());
let user_input = readline_edit(prompt, history, completer);
try!(disable_raw_mode(original_termios));
println!("");
user_input
}
fn readline_direct() -> Result<String> {
let mut line = String::new();
try!(io::stdin().read_line(&mut line));
Ok(line)
}
/// Line editor
pub struct Editor<'completer> {
unsupported_term: bool,
stdin_isatty: bool,
//cols: usize, // Number of columns in terminal
history: History,
completer: Option<&'completer Completer>,
}
impl<'completer> Editor<'completer> {
pub fn new() -> Editor<'completer> {
// TODO check what is done in rl_initialize()
// if the number of columns is stored here, we need a SIGWINCH handler...
// if enable_raw_mode is called here, we need to implement Drop to reset the terminal in its original state...
Editor{
unsupported_term: is_unsupported_term(),
stdin_isatty: is_a_tty(),
history: History::new(),
completer: None}
}
/// This method will read a line from STDIN and will display a `prompt`
pub fn readline(&mut self, prompt: &str) -> Result<String> {
if self.unsupported_term {
// Write prompt and flush it to stdout
let mut stdout = io::stdout();
try!(write_and_flush(&mut stdout, prompt.as_bytes()));
readline_direct()
} else if !self.stdin_isatty { // Not a tty: read from file / pipe.
readline_direct()
} else {
readline_raw(prompt, &mut self.history, self.completer)
}
}
/// 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(&mut self, line: &str) -> bool {
self.history.add(line)
}
/// Set the maximum length for the history.
pub fn set_history_max_len(&mut self, max_len: usize) {
self.history.set_max_len(max_len)
}
/// Clear history.
pub fn clear_history(&mut self) {
self.history.clear()
}
/// Register a callback function to be called for tab-completion.
pub fn set_completer(&mut self, completer: Option<&'completer Completer>) {
self.completer = completer;
}
}
#[cfg(test)]
mod test {
use std::io::Write;
use history::History;
use completion::Completer;
use State;
fn init_state<'out>(out: &'out mut Write, line: &str, pos: usize, cols: usize) -> State<'out, 'static> {
State {
out : out,
prompt: "",
prompt_width: 0,
buf: String::from(line),
pos: pos,
cols: cols,
history_index: 0,
history_end: String::new(),
bytes: [0; 4],
}
}
#[test]
fn insert() {
let mut out = ::std::io::sink();
let mut s = State::new(&mut out, "", 128, 80, 0);
super::edit_insert(&mut s, 'α').unwrap();
assert_eq!("α", s.buf);
assert_eq!(2, s.pos);
super::edit_insert(&mut s, 'ß').unwrap();
assert_eq!("αß", s.buf);
assert_eq!(4, s.pos);
s.pos = 0;
super::edit_insert(&mut s, 'γ').unwrap();
assert_eq!("γαß", s.buf);
assert_eq!(2, s.pos);
}
#[test]
fn moves() {
let mut out = ::std::io::sink();
let mut s = init_state(&mut out, "αß", 4, 80);
super::edit_move_left(&mut s).unwrap();
assert_eq!("αß", s.buf);
assert_eq!(2, s.pos);
super::edit_move_right(&mut s).unwrap();
assert_eq!("αß", s.buf);
assert_eq!(4, s.pos);
super::edit_move_home(&mut s).unwrap();
assert_eq!("αß", s.buf);
assert_eq!(0, s.pos);
super::edit_move_end(&mut s).unwrap();
assert_eq!("αß", s.buf);
assert_eq!(4, s.pos);
}
#[test]
fn delete() {
let mut out = ::std::io::sink();
let mut s = init_state(&mut out, "αß", 2, 80);
super::edit_delete(&mut s).unwrap();
assert_eq!("α", s.buf);
assert_eq!(2, s.pos);
super::edit_backspace(&mut s).unwrap();
assert_eq!("", s.buf);
assert_eq!(0, s.pos);
}
#[test]
fn kill() {
let mut out = ::std::io::sink();
let mut s = init_state(&mut out, "αßγδε", 6, 80);
super::edit_kill_line(&mut s).unwrap();
assert_eq!("αßγ", s.buf);
assert_eq!(6, s.pos);
s.pos = 4;
super::edit_discard_line(&mut s).unwrap();
assert_eq!("γ", s.buf);
assert_eq!(0, s.pos);
}
#[test]
fn transpose() {
let mut out = ::std::io::sink();
let mut s = init_state(&mut out, "aßc", 1, 80);
super::edit_transpose_chars(&mut s).unwrap();
assert_eq!("ßac", s.buf);
assert_eq!(3, s.pos);
s.buf = String::from("aßc");
s.pos = 3;
super::edit_transpose_chars(&mut s).unwrap();
assert_eq!("acß", s.buf);
assert_eq!(2, s.pos);
}
#[test]
fn delete_prev_word() {
let mut out = ::std::io::sink();
let mut s = init_state(&mut out, "a ß c", 6, 80);
super::edit_delete_prev_word(&mut s).unwrap();
assert_eq!("a c", s.buf);
assert_eq!(2, s.pos);
}
#[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, 80);
let mut history = History::new();
history.add("line0");
history.add("line1");
s.history_index = history.len();
s.buf = String::from(line);
for _ in 0..2 {
super::edit_history_next(&mut s, &mut history, false).unwrap();
assert_eq!(line, s.buf);
}
super::edit_history_next(&mut s, &mut history, true).unwrap();
assert_eq!(line, s.history_end);
assert_eq!(1, s.history_index);
assert_eq!("line1", s.buf);
for _ in 0..2 {
super::edit_history_next(&mut s, &mut history, true).unwrap();
assert_eq!(line, s.history_end);
assert_eq!(0, s.history_index);
assert_eq!("line0", s.buf);
}
super::edit_history_next(&mut s, &mut history, false).unwrap();
assert_eq!(line, s.history_end);
assert_eq!(1, s.history_index);
assert_eq!("line1", s.buf);
super::edit_history_next(&mut s, &mut history, false).unwrap();
assert_eq!(line, s.history_end);
assert_eq!(2, s.history_index);
assert_eq!(line, s.buf);
}
struct SimpleCompleter;
impl Completer for SimpleCompleter {
fn complete(&self, line: &str, _pos: usize) -> Vec<String> {
vec!(line.to_string() + "t")
}
}
#[test]
fn complete_line() {
use std::io::Read;
let mut out = ::std::io::sink();
let mut s = init_state(&mut out, "rus", 3, 80);
let input = b"\n";
let mut chars = input.chars();
let completer = SimpleCompleter;
let ch = super::complete_line(&mut chars, &mut s, &completer).unwrap();
assert_eq!(Some('\n'), ch);
assert_eq!("rust", s.buf);
assert_eq!(4, s.pos);
}
}