extern crate linefeed;
#[macro_use] extern crate assert_matches;
use std::env::set_var;
use std::io;
use std::sync::Arc;
use linefeed::{Command, Completer, Completion, Interface, Prompter, ReadResult};
use linefeed::memory::MemoryTerminal;
use linefeed::terminal::{Size, Terminal};
const UP_ARROW: &str = "\x1b[A";
const DOWN_ARROW: &str = "\x1b[B";
const RIGHT_ARROW: &str = "\x1b[C";
const LEFT_ARROW: &str = "\x1b[D";
const HOME: &str = "\x1b[H";
const INSERT: &str = "\x1b[2~";
const DELETE: &str = "\x1b[3~";
fn test(input: &str) -> (MemoryTerminal, Interface<MemoryTerminal>) {
let term = MemoryTerminal::with_size(Size{columns: 20, lines: 5});
term.push_input(input);
set_var("INPUTRC", "");
let interface = Interface::with_term("test", term.clone()).unwrap();
interface.set_prompt("$ ").unwrap();
(term, interface)
}
fn assert_lines(term: &MemoryTerminal, tests: &[&str]) {
let mut lines = term.lines();
let mut tests = tests.iter();
while let Some(line) = lines.next() {
let test = match tests.next() {
Some(test) => test,
None => ""
};
let end = match line.iter().rposition(|&ch| ch != ' ') {
Some(pos) => pos + 1,
None => 0
};
if line[..end].iter().cloned().ne(test.chars()) {
let line = line[..end].iter().cloned().collect::<String>();
panic!("terminal line doesn't match: line={:?}; test={:?}", line, test);
}
}
}
fn assert_read<T: Terminal>(r: &Interface<T>, line: &str) {
assert_matches!(r.read_line(), Ok(ReadResult::Input(ref s)) if s == line);
}
#[test]
fn test_eof() {
let (term, r) = test("\x04");
assert_matches!(r.read_line(), Ok(ReadResult::Eof));
term.push_input("foo\x04\n");
assert_read(&r, "foo");
assert_lines(&term, &["$", "$ foo"]);
}
#[test]
fn test_quote() {
let (term, r) = test("\x16\x03\n");
assert_read(&r, "\x03");
assert_lines(&term, &["$ ^C"]);
}
#[test]
fn test_insert() {
let (term, r) = test("abc\n");
assert_read(&r, "abc");
assert_lines(&term, &["$ abc"]);
}
struct TestCompleter(Vec<&'static str>);
impl<Term: Terminal> Completer<Term> for TestCompleter {
fn complete(&self, _word: &str, _reader: &Prompter<Term>,
_start: usize, _end: usize) -> Option<Vec<Completion>> {
Some(self.0.clone().into_iter()
.map(|s| Completion::simple(s.to_owned())).collect())
}
}
#[test]
fn test_complete() {
let (term, r) = test("hi foo\t\t\n");
r.set_completer(Arc::new(TestCompleter(vec!["foobar", "foobaz"])));
assert_read(&r, "hi fooba");
assert_lines(&term, &["$ hi fooba", "foobar foobaz", "$ hi fooba"]);
term.clear_all();
term.push_input("hi foo\x1b?\n");
assert_read(&r, "hi foo");
assert_lines(&term, &["$ hi foo", "foobar foobaz", "$ hi foo"]);
term.clear_all();
term.push_input("hi foo\x1b*\n");
assert_read(&r, "hi foobar foobaz ");
assert_lines(&term, &["$ hi foobar foobaz"]);
}
fn fn_foo<Term: Terminal>(reader: &mut Prompter<Term>, count: i32, ch: char)
-> io::Result<()> {
assert_eq!(count, 1);
assert_eq!(ch, '\x18');
assert!(!reader.explicit_arg());
reader.insert_str("foo")
}
fn fn_bar<Term: Terminal>(reader: &mut Prompter<Term>, count: i32, ch: char)
-> io::Result<()> {
assert_eq!(count, 2);
assert_eq!(ch, '\x19');
assert!(reader.explicit_arg());
reader.insert_str("bar")
}
#[test]
fn test_function() {
let (term, r) = test("");
r.define_function("fn-foo", Arc::new(fn_foo));
r.bind_sequence("\x18", Command::from_str("fn-foo"));
r.define_function("fn-bar", Arc::new(fn_bar));
r.bind_sequence("\x19", Command::from_str("fn-bar"));
term.push_input("\x18\n");
assert_read(&r, "foo");
term.push_input("\x1b2\x19\n");
assert_read(&r, "bar");
assert_lines(&term, &["$ foo", "$ bar"]);
}
#[test]
fn test_macro() {
let (term, r) = test("");
r.bind_sequence("A", Command::Macro("foo" .into()));
r.bind_sequence("B", Command::Macro("barCquux".into()));
r.bind_sequence("C", Command::Macro("baz" .into()));
term.push_input("A\n");
assert_read(&r, "foo");
term.push_input("B\n");
assert_read(&r, "barbazquux");
assert_lines(&term, &["$ foo", "$ barbazquux"]);
}
#[test]
fn test_comment() {
let (term, r) = test("lol\x1b#");
assert_read(&r, "#lol");
assert_lines(&term, &["$ #lol"]);
term.clear_all();
term.push_input("#wut\x1b-\x1b#");
assert_read(&r, "wut");
assert_lines(&term, &["$ wut"]);
}
#[test]
fn test_arrows() {
let (term, r) = test("abcde");
term.push_input(LEFT_ARROW);
term.push_input("x");
term.push_input(HOME);
term.push_input("y");
term.push_input(RIGHT_ARROW);
term.push_input("z\n");
assert_read(&r, "yazbcdxe");
term.push_input("abcde");
term.push_input("\x1b3");
term.push_input(LEFT_ARROW);
term.push_input("x\n");
assert_read(&r, "abxcde");
assert_lines(&term, &["$ yazbcdxe", "$ abxcde"]);
}
#[test]
fn test_digit() {
let (term, r) = test("");
term.push_input("\x1b10.\n");
assert_read(&r, "..........");
assert_lines(&term, &["$ .........."]);
}
#[test]
fn test_search_char() {
let (term, r) = test("lolwut");
term.push_input("\x1b\x1dw.");
term.push_input("\x1b2\x1b\x1dl,");
term.push_input("\x1do:\n");
assert_read(&r, ",l:ol.wut");
term.push_input("alpha");
term.push_input(HOME);
term.push_input("\x1dax\n");
assert_read(&r, "alphxa");
assert_lines(&term, &["$ ,l:ol.wut", "$ alphxa"]);
}
#[test]
fn test_delete() {
let (term, r) = test("sup");
term.push_input(LEFT_ARROW);
term.push_input(LEFT_ARROW);
term.push_input(DELETE);
term.push_input("\n");
assert_read(&r, "sp");
term.push_input("sup");
term.push_input(LEFT_ARROW);
term.push_input(LEFT_ARROW);
term.push_input("\x7f\n");
assert_read(&r, "up");
assert_lines(&term, &["$ sp", "$ up"]);
}
#[test]
fn test_history() {
let (term, r) = test("");
r.add_history("alpha".to_owned());
r.add_history("bravo".to_owned());
r.add_history("charlie".to_owned());
r.add_history("delta".to_owned());
term.push_input(UP_ARROW);
term.push_input("\n");
assert_read(&r, "delta");
term.push_input(UP_ARROW);
term.push_input(UP_ARROW);
term.push_input("\n");
assert_read(&r, "charlie");
term.push_input("foo");
term.push_input(UP_ARROW);
term.push_input(DOWN_ARROW);
term.push_input("\n");
assert_read(&r, "foo");
assert_lines(&term, &["$ delta", "$ charlie", "$ foo"]);
}
#[test]
fn test_history_mod() {
let (term, r) = test("");
r.add_history("alpha".to_owned());
r.add_history("bravo".to_owned());
r.add_history("charlie".to_owned());
r.add_history("delta".to_owned());
term.push_input(UP_ARROW);
term.push_input("x");
term.push_input(UP_ARROW);
term.push_input("x\n");
assert_read(&r, "charliex");
term.push_input(UP_ARROW);
term.push_input("\n");
assert_read(&r, "deltax");
term.push_input(UP_ARROW);
term.push_input(UP_ARROW);
term.push_input("\n");
assert_read(&r, "charlie");
assert_lines(&term, &["$ charliex", "$ deltax", "$ charlie"]);
}
#[test]
fn test_kill() {
let (term, r) = test("foo bar baz\x1b\x7f\x1b\x7f\n");
assert_read(&r, "foo ");
term.push_input("\x19\n");
assert_read(&r, "bar baz");
assert_lines(&term, &["$ foo", "$ bar baz"]);
term.clear_all();
term.push_input("alpha beta gamma\x1b\x7f");
term.push_input(" \x7f"); term.push_input("\x1b\x7f\n");
assert_read(&r, "alpha ");
term.push_input("\x19\x19\x1by\n");
assert_read(&r, "beta gamma");
assert_lines(&term, &["$ alpha", "$ beta gamma"]);
}
#[test]
fn test_overwrite() {
let (term, r) = test("foo");
term.push_input(LEFT_ARROW);
term.push_input(LEFT_ARROW);
term.push_input(INSERT);
term.push_input("xxx\n");
assert_read(&r, "fxxx");
assert_lines(&term, &["$ fxxx"]);
}
#[test]
fn test_transpose_chars() {
let (term, r) = test("");
term.push_input("abc\x14x\n");
assert_read(&r, "acbx");
term.push_input("abcde");
term.push_input(HOME);
term.push_input(RIGHT_ARROW);
term.push_input("\x14\x14\n");
assert_read(&r, "bcade");
term.push_input("abcde");
term.push_input("\x1b-3\x14x\n");
assert_read(&r, "aexbcd");
term.push_input("abcde");
term.push_input(HOME);
term.push_input(RIGHT_ARROW);
term.push_input("\x1b3\x14x\n");
assert_read(&r, "bcdaxe");
assert_lines(&term, &["$ acbx", "$ bcade", "$ aexbcd", "$ bcdaxe"]);
}
#[test]
fn test_transpose_words() {
let (term, r) = test("");
term.resize(Size{lines: 7, columns: 40});
term.push_input("a bb ccc");
term.push_input("\x1btx\n");
assert_read(&r, "a ccc bbx");
term.push_input("a bb ccc");
term.push_input("\x1b-\x1btx\n");
assert_read(&r, "a cccx bb");
term.push_input("a bb ccc");
term.push_input(HOME);
term.push_input(RIGHT_ARROW);
term.push_input("\x1btx\n");
assert_read(&r, "bb ax ccc");
term.push_input("a bb ccc");
term.push_input(HOME);
term.push_input(RIGHT_ARROW);
term.push_input("\x1bt\x1btx\n");
assert_read(&r, "bb ccc ax");
term.push_input("a bb ccc dddd eeeee");
term.push_input(HOME);
term.push_input(RIGHT_ARROW);
term.push_input("\x1b3\x1btx\n");
assert_read(&r, "bb ccc dddd ax eeeee");
term.push_input("a bb ccc dddd eeeee");
term.push_input("\x1b-3\x1btx\n");
assert_read(&r, "a eeeeex bb ccc dddd");
assert_lines(&term, &["$ a ccc bbx", "$ a cccx bb", "$ bb ax ccc",
"$ bb ccc ax", "$ bb ccc dddd ax eeeee", "$ a eeeeex bb ccc dddd"]);
}
#[test]
fn test_search_history() {
let (term, r) = test("");
term.resize(Size{lines: 10, columns: 10});
r.add_history("foo".into());
term.push_input("\x12f\n");
assert_read(&r, "foo");
r.add_history("bar veryverylonginput".into());
term.push_input("\x12b\n");
assert_read(&r, "bar veryverylonginput");
assert_lines(&term, &[
"$ foo",
"$ bar very",
"verylongin",
"put",
]);
}
#[test]
fn test_history_search() {
let (term, r) = test("");
term.resize(Size{lines: 10, columns: 10});
r.bind_sequence("\x01", Command::HistorySearchBackward);
r.bind_sequence("\x02", Command::HistorySearchForward);
r.add_history("foo".into());
r.add_history("fab".into());
r.add_history("fun".into());
term.push_input("f\x01\n");
assert_read(&r, "fun");
term.push_input("f\x01\x01\n");
assert_read(&r, "fab");
term.push_input("f\x01\x01\x01\n");
assert_read(&r, "foo");
term.push_input("f\x01\x01\x02\n");
assert_read(&r, "fun");
term.push_input("f\x01\x02\x02\n");
assert_read(&r, "fun");
assert_lines(&term, &[
"$ fun",
"$ fab",
"$ foo",
"$ fun",
"$ fun",
]);
}