zaile
zaile

Reputation: 193

How do I enable editable user input for a Rust terminal application?

I am writing a minimal Lisp with a classic terminal-based REPL environment project in Rust.

How do I read in user input from the arrow keys, allowing them to move back and forth on their current line of input at least before they have pressed enter? Ideally, I will be able to extend functionality to include moving "back" to retrieve old inputs like you would in any terminal or any REPL. Here's an image of the behavior for clarity:

enter image description here

I have played around with the standard library's io module and the termion crate but have not figured this functionality out.

Here is my current working code. It effectively takes input and immediately prints it back to the user in addition to quitting as expected with quit().

use std::io::prelude::*;
use std::io;

fn main() {
    println!("Rispy Version 0.0.1");
    println!("Enter `quit()` to Exit");

    let mut input: String;

    // continuous input til ctrl-c or quit()
    loop {

        print!("rispy>> ");
        io::stdout().flush().unwrap();

        input = String::new();
        io::stdin().read_line(&mut input)
            .expect("Error reading line");

        print!("input: {}", input);

        match input.as_ref() {
            "quit()\n" => {
                println!("\nGoodbye");
                break;
            },
            _ => continue,
        }
    }
}

Upvotes: 2

Views: 1898

Answers (2)

Shepmaster
Shepmaster

Reputation: 431689

As an out-of-the-box answer, I occasionally use rlwrap, a small wrapper around any command line program that adds basic readline capabilities.

Running rlwrap cargo run, your original program now has editing like you asked, as well as command history and history searching, and probably a lot of other things.

Upvotes: 2

zaile
zaile

Reputation: 193

It's funny how some basic/fundamental crates are not suggested elsewhere more readily for problems like this but thankfully @kazemakase answered the question by suggesting a crate I had not found up to this point: rustyline

A slight edit to the example code on the readme yields the results I want, with history, the ability to navigate left/right with the arrow keys, and even the use of key strokes like ctrl-d, ctrl-c, home, etcetera. Here it is to couple with the question as asked:

extern crate rustyline;

use rustyline::Editor;
use rustyline::error::ReadlineError;

fn main() {
    println!("Rispy Version 0.0.1");
    println!("Enter `quit()` to Exit");

    let mut reader = Editor::<()>::new();
    if let Err(_) = reader.load_history("rispy_history.txt") {
        println!("No previous history.");
    }

    // continuous input
    loop {

        let readline = reader.readline("rispy>> ");

        match readline {
            Ok(line) => {
                reader.add_history_entry(&line);
                println!("input: {}", line);
            },
            Err(ReadlineError::Interrupted) => {
                println!("CTRL-C");
                println!("Goodbye");
                break
            }
            Err(ReadlineError::Eof) => {
                println!("CTRL-D");
                println!("Goodbye");
                break
            },
            Err(err) => {
                println!("Error: {:?}", err);
                break
            }
        }
    }
    reader.save_history("rispy_history.txt").unwrap();
}

Upvotes: 5

Related Questions