From a933ae8ef3e0926c5bb00f65dd6b81bf57aa096d Mon Sep 17 00:00:00 2001 From: Iovoslav Iovchev <30962174+IovoslavIovchev@users.noreply.github.com> Date: Wed, 8 Jul 2020 13:19:46 +0300 Subject: [PATCH] rustyline for the cli (#492) Co-authored-by: Iban Eguia --- .gitignore | 1 + Cargo.lock | 124 ++++++++++++++++++++++++++++++++++++++++++++ boa_cli/Cargo.toml | 1 + boa_cli/src/main.rs | 67 ++++++++++++++++-------- 4 files changed, 170 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 972d1eb0f84..57085344047 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ yarn-error.log # tests/js/test.js is used for testing changes locally tests/js/test.js +.boa_history # Profiling *.string_data diff --git a/Cargo.lock b/Cargo.lock index 36214179c49..c54d6d7d021 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + [[package]] name = "atty" version = "0.2.14" @@ -55,18 +67,36 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + [[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "blake2b_simd" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "boa_cli" version = "0.9.0" dependencies = [ "Boa", "jemallocator", + "rustyline", "serde_json", "structopt", ] @@ -148,6 +178,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "criterion" version = "0.3.2" @@ -253,6 +289,27 @@ dependencies = [ "memchr", ] +[[package]] +name = "dirs-next" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cbcf9241d9e8d106295bd496bbe2e9cffd5fa098f2a8c9e2bbcbf09773c11a8" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c60f7b8a8953926148223260454befb50c751d3c50e1c178c4fd1ace4083c9a" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "either" version = "1.5.3" @@ -433,6 +490,19 @@ dependencies = [ "autocfg", ] +[[package]] +name = "nix" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "void", +] + [[package]] name = "num-bigint" version = "0.3.0" @@ -646,6 +716,17 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +[[package]] +name = "redox_users" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +dependencies = [ + "getrandom", + "redox_syscall", + "rust-argon2", +] + [[package]] name = "regex" version = "1.3.9" @@ -673,6 +754,18 @@ version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" +[[package]] +name = "rust-argon2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -688,6 +781,25 @@ dependencies = [ "semver", ] +[[package]] +name = "rustyline" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3358c21cbbc1a751892528db4e1de4b7a2b6a73f001e215aaba97d712cfa9777" +dependencies = [ + "cfg-if", + "dirs-next", + "libc", + "log", + "memchr", + "nix", + "scopeguard", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + [[package]] name = "ryu" version = "1.0.5" @@ -874,6 +986,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + [[package]] name = "vec_map" version = "0.8.2" @@ -886,6 +1004,12 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "walkdir" version = "2.3.1" diff --git a/boa_cli/Cargo.toml b/boa_cli/Cargo.toml index dbe363730fc..bf9dd0db125 100644 --- a/boa_cli/Cargo.toml +++ b/boa_cli/Cargo.toml @@ -12,6 +12,7 @@ edition = "2018" [dependencies] Boa = { path = "../boa", features = ["serde"] } +rustyline = "6.2.0" structopt = "0.3.15" serde_json = "1.0.56" diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index e3b44cf3e97..8dee2832c80 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -31,11 +31,8 @@ use boa::{ realm::Realm, syntax::ast::{node::StatementList, token::Token}, }; -use std::{ - fs::read_to_string, - io::{self, Write}, - path::PathBuf, -}; +use rustyline::{config::Config, error::ReadlineError, EditMode, Editor}; +use std::{fs::read_to_string, path::PathBuf}; use structopt::{clap::arg_enum, StructOpt}; #[cfg(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"))] @@ -46,7 +43,8 @@ use structopt::{clap::arg_enum, StructOpt}; static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; /// CLI configuration for Boa. -// +static CLI_HISTORY: &str = ".boa_history"; + // Added #[allow(clippy::option_option)] because to StructOpt an Option> // is an optional argument that optionally takes a value ([--opt=[val]]). // https://docs.rs/structopt/0.3.11/structopt/#type-magic @@ -78,6 +76,10 @@ struct Opt { case_insensitive = true )] dump_ast: Option>, + + /// Use vi mode in the REPL + #[structopt(long = "vi")] + vi_mode: bool, } impl Opt { @@ -182,9 +184,8 @@ pub fn main() -> Result<(), std::io::Error> { let buffer = read_to_string(file)?; if args.has_dump_flag() { - match dump(&buffer, &args) { - Ok(_) => {} - Err(e) => eprintln!("{}", e), + if let Err(e) = dump(&buffer, &args) { + eprintln!("{}", e); } } else { match forward_val(&mut engine, &buffer) { @@ -195,26 +196,46 @@ pub fn main() -> Result<(), std::io::Error> { } if args.files.is_empty() { - loop { - let mut buffer = String::new(); + let config = Config::builder() + .keyseq_timeout(1) + .edit_mode(if args.vi_mode { + EditMode::Vi + } else { + EditMode::Emacs + }) + .build(); - io::stdin().read_line(&mut buffer)?; + let mut editor = Editor::<()>::with_config(config); + let _ = editor.load_history(CLI_HISTORY); - if args.has_dump_flag() { - match dump(&buffer, &args) { - Ok(_) => {} - Err(e) => eprintln!("{}", e), + loop { + match editor.readline("> ") { + Ok(line) if line == ".exit" => break, + Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => break, + + Ok(line) => { + editor.add_history_entry(&line); + + if args.has_dump_flag() { + if let Err(e) = dump(&line, &args) { + eprintln!("{}", e); + } + } else { + match forward_val(&mut engine, line.trim_end()) { + Ok(v) => println!("{}", v), + Err(v) => eprintln!("{}", v), + } + } } - } else { - match forward_val(&mut engine, buffer.trim_end()) { - Ok(v) => println!("{}", v.to_string()), - Err(v) => eprintln!("{}", v.to_string()), + + Err(err) => { + eprintln!("Unknown error: {:?}", err); + break; } } - - // The flush is needed because where in a REPL and we do not want buffering. - std::io::stdout().flush().unwrap(); } + + editor.save_history(CLI_HISTORY).unwrap(); } Ok(())