rustyline 19.0.0

Rustyline, a readline implementation based on Antirez's Linenoise
Documentation
use std::vec::IntoIter;

use crate::completion::Completer;
use crate::config::{CompletionType, Config, Configurer as _, EditMode};
use crate::edit::Ed;
use crate::highlight::Highlighter;
use crate::hint::Hinter;
use crate::history::History as _;
use crate::keymap::{Cmd, Is};
use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
use crate::validate::Validator;
use crate::{Context, DefaultEditor, Helper, Result, apply_backspace_direct, readline_direct};

mod common;
mod emacs;
mod history;
mod vi_cmd;
mod vi_insert;

fn init_editor(mode: EditMode, keys: &[KeyEvent]) -> DefaultEditor {
    let config = Config::builder().edit_mode(mode).build();
    let mut editor = DefaultEditor::with_config(config).unwrap();
    editor.term.keys.extend(keys.iter().copied());
    editor
}

struct SimpleCompleter;
impl Completer for SimpleCompleter {
    type Candidate = String;

    fn complete(
        &self,
        line: &str,
        _pos: usize,
        _ctx: &Context<'_>,
    ) -> Result<(usize, Vec<String>)> {
        Ok((
            0,
            if line == "rus" {
                vec![line.to_owned() + "t"]
            } else if line == "\\hbar" {
                vec!["ℏ".to_owned()]
            } else if line == "ru" {
                vec!["rust".to_owned(), "rush".to_owned()]
            } else {
                vec![]
            },
        ))
    }
}
impl Hinter for SimpleCompleter {
    type Hint = String;
}
impl Helper for SimpleCompleter {}
impl Highlighter for SimpleCompleter {}
impl Validator for SimpleCompleter {}

#[test]
fn complete_line() {
    let mut ed = Ed::new(SimpleCompleter);
    let mut s = ed.init_state("rus", 3);
    let is = Is::default();
    let mut input_state = is.input_state();
    let keys = vec![E::ENTER];
    let mut rdr: IntoIter<KeyEvent> = keys.into_iter();
    let cmd = super::complete_line(&mut rdr, &mut s, &mut input_state, &is.config).unwrap();
    assert_eq!(
        Some(Cmd::AcceptOrInsertLine {
            accept_in_the_middle: true
        }),
        cmd
    );
    assert_eq!("rust", s.line.as_str());
    assert_eq!(4, s.line.pos());
}

#[test]
fn complete_symbol() {
    let mut ed = Ed::new(SimpleCompleter);
    let mut s = ed.init_state("\\hbar", 5);
    let mut is = Is::default();
    is.config.set_completion_type(CompletionType::List);
    let mut input_state = is.input_state();
    let keys = vec![E::ENTER];
    let mut rdr: IntoIter<KeyEvent> = keys.into_iter();
    let cmd = super::complete_line(&mut rdr, &mut s, &mut input_state, &is.config).unwrap();
    assert_eq!(None, cmd);
    assert_eq!("ℏ", s.line.as_str());
    assert_eq!(3, s.line.pos());
}

#[test]
fn complete_tab_confirms_display_all() {
    let mut ed = Ed::new(SimpleCompleter);
    let mut s = ed.init_state("ru", 2);
    let mut is = Is::default();
    is.config.set_completion_type(CompletionType::List);
    is.config.set_completion_prompt_limit(1);
    let mut input_state = is.input_state();
    // First `Tab` triggers the "Display all N possibilities?" prompt,
    // the second `Tab` answers it (like `y`).
    let keys = vec![E(K::Tab, M::NONE), E(K::Tab, M::NONE)];
    let mut rdr: IntoIter<KeyEvent> = keys.into_iter();
    let cmd = super::complete_line(&mut rdr, &mut s, &mut input_state, &is.config).unwrap();
    assert_eq!(None, cmd);
    assert_eq!("rus", s.line.as_str());
}

// `keys`: keys to press
// `expected_line`: line after enter key
fn assert_line(mode: EditMode, keys: &[KeyEvent], expected_line: &str) {
    let mut editor = init_editor(mode, keys);
    let actual_line = editor.readline(">>").unwrap();
    assert_eq!(expected_line, actual_line);
}

// `initial`: line status before `keys` pressed: strings before and after cursor
// `keys`: keys to press
// `expected_line`: line after enter key
fn assert_line_with_initial(
    mode: EditMode,
    initial: (&str, &str),
    keys: &[KeyEvent],
    expected_line: &str,
) {
    let mut editor = init_editor(mode, keys);
    let actual_line = editor.readline_with_initial(">>", initial).unwrap();
    assert_eq!(expected_line, actual_line);
}

// `initial`: line status before `keys` pressed: strings before and after cursor
// `keys`: keys to press
// `expected`: line status before enter key: strings before and after cursor
fn assert_cursor(mode: EditMode, initial: (&str, &str), keys: &[KeyEvent], expected: (&str, &str)) {
    let mut editor = init_editor(mode, keys);
    let actual_line = editor.readline_with_initial("", initial).unwrap();
    assert_eq!(expected.0.to_owned() + expected.1, actual_line);
    assert_eq!(expected.0.len(), editor.term.cursor);
}

// `entries`: history entries before `keys` pressed
// `keys`: keys to press
// `expected`: line status before enter key: strings before and after cursor
fn assert_history(
    mode: EditMode,
    entries: &[&str],
    keys: &[KeyEvent],
    prompt: &str,
    expected: (&str, &str),
) {
    let mut editor = init_editor(mode, keys);
    for entry in entries {
        editor.history.add(entry).unwrap();
    }
    let actual_line = editor.readline(prompt).unwrap();
    assert_eq!(expected.0.to_owned() + expected.1, actual_line);
    if prompt.is_empty() {
        assert_eq!(expected.0.len(), editor.term.cursor);
    }
}

#[test]
fn unknown_esc_key() {
    for mode in &[EditMode::Emacs, EditMode::Vi] {
        assert_line(*mode, &[E(K::UnknownEscSeq, M::NONE), E::ENTER], "");
    }
}

#[test]
fn test_send() {
    fn assert_send<T: Send>() {}
    assert_send::<DefaultEditor>();
}

#[test]
fn test_sync() {
    fn assert_sync<T: Sync>() {}
    assert_sync::<DefaultEditor>();
}

#[test]
fn test_apply_backspace_direct() {
    assert_eq!(
        &apply_backspace_direct("Hel\u{0008}\u{0008}el\u{0008}llo ☹\u{0008}☺"),
        "Hello ☺"
    );
}

#[test]
fn test_readline_direct() {
    use std::io::Cursor;

    let mut write_buf = vec![];
    let output = readline_direct(
        Cursor::new("([)\n\u{0008}\n\n\r\n])".as_bytes()),
        Cursor::new(&mut write_buf),
        Some(crate::validate::MatchingBracketValidator::new()).as_ref(),
    );

    assert_eq!(
        &write_buf,
        b"Mismatched brackets: '[' is not properly closed"
    );
    assert_eq!(&output.unwrap(), "([\n\n\r\n])");
}