diff --git a/.gitignore b/.gitignore index 897cc4d5..afe79602 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ snapstore.bin snapstore.partmap /snapshots /.idea -.DS_Store \ No newline at end of file +.DS_Store +.sky_history \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6d89757a..ac19328b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,12 +97,64 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "crossterm" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c" +dependencies = [ + "bitflags", + "crossterm_winapi", + "lazy_static", + "libc", + "mio", + "parking_lot", + "signal-hook", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9" +dependencies = [ + "winapi", +] + [[package]] name = "devtimer" version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "907339959a92f6b98846570500c0a567c9aecbb3871cef00561eb5d20d47b7c1" +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "env_logger" version = "0.8.3" @@ -131,6 +183,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs_extra" version = "1.2.0" @@ -360,6 +422,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -540,6 +623,16 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.3" @@ -589,6 +682,16 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + [[package]] name = "regex" version = "1.4.6" @@ -606,6 +709,29 @@ version = "0.6.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" +[[package]] +name = "rustyline" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e1b597fcd1eeb1d6b25b493538e5aa19629eb08932184b85fef931ba87e893" +dependencies = [ + "bitflags", + "cfg-if", + "dirs-next", + "fs2", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "scopeguard", + "smallvec", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + [[package]] name = "ryu" version = "1.0.5" @@ -649,6 +775,17 @@ dependencies = [ "serde", ] +[[package]] +name = "signal-hook" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" +dependencies = [ + "libc", + "mio", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.3.0" @@ -714,8 +851,10 @@ version = "0.5.1" dependencies = [ "bytes", "clap", + "crossterm", "libsky", "openssl", + "rustyline", "tokio", "tokio-openssl", ] @@ -841,6 +980,12 @@ dependencies = [ "serde", ] +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + [[package]] name = "unicode-width" version = "0.1.8" @@ -853,6 +998,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 = "vcpkg" version = "0.2.11" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 3edb2ac0..ef63ec9b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -12,4 +12,6 @@ tokio = {version = "1.5.0", features = ["full"]} bytes = "1.0.1" clap = {version = "2.33.3", features=["yaml"]} openssl = { version = "0.10.33", features = ["vendored"] } -tokio-openssl = "0.6.1" \ No newline at end of file +tokio-openssl = "0.6.1" +rustyline = "8.0.0" +crossterm = "0.19.0" \ No newline at end of file diff --git a/cli/src/argparse.rs b/cli/src/argparse.rs index a8ec05a5..04460900 100644 --- a/cli/src/argparse.rs +++ b/cli/src/argparse.rs @@ -27,15 +27,20 @@ use crate::protocol; use clap::load_yaml; use clap::App; +use std::io::stdout; use libsky::terrapipe::ADDR; +use crossterm::{execute, cursor}; +use crossterm::terminal::{Clear, ClearType}; use protocol::{Con, Connection, SslConnection}; -use std::io::{self, prelude::*}; +use readline::config::Configurer; +use readline::{error::ReadlineError, Editor}; +use rustyline as readline; use std::process; const MSG_WELCOME: &'static str = "Skytable v0.5.1"; #[macro_use] -macro_rules! end_con_with_loop { - ($con:ident) => { +macro_rules! close_con { + ($con:expr) => { if let Err(e) = $con.shutdown().await { eprintln!( "Failed to gracefully terminate connection with error '{}'", @@ -43,7 +48,10 @@ macro_rules! end_con_with_loop { ); std::process::exit(0x100); } - break; + }; + ($con:expr, $err:expr) => { + eprintln!("An error occurred while reading your input: '{}'", $err); + close_con!($con) }; } @@ -97,29 +105,33 @@ pub async fn start_repl() { con.execute_query(eval_expr.to_string()).await; return; } + let mut editor = Editor::<()>::new(); + editor.set_auto_add_history(true); + editor.set_history_ignore_dups(true); + let _ = editor.load_history(".sky_history"); println!("{}", MSG_WELCOME); loop { - print!("skysh>"); - io::stdout() - .flush() - .expect("Couldn't flush buffer, this is a serious error!"); - let mut rl = String::new(); - io::stdin() - .read_line(&mut rl) - .expect("Couldn't read line, this is a serious error!"); - if rl.trim().to_uppercase() == "EXIT" { - println!("Goodbye!"); - end_con_with_loop!(con); - } - if rl.len() == 0 { - // The query was empty, so let it be - continue; - } - tokio::select! { - _ = con.execute_query(rl) => {}, - _ = tokio::signal::ctrl_c() => { - end_con_with_loop!(con); + match editor.readline("skysh> ") { + Ok(line) => match line.to_lowercase().as_str() { + "exit" => break, + "clear" => { + let mut stdout = stdout(); + execute!(stdout, Clear(ClearType::All)).expect("Failed to clear screen"); + execute!(stdout, cursor::MoveTo(0, 0)).expect("Failed to move cursor to origin"); + drop(stdout); // aggressively drop stdout + continue; + } + _ => con.execute_query(line).await, }, + Err(ReadlineError::Interrupted) => break, + Err(err) => { + close_con!(con, err); + } } } + if let Err(e) = editor.save_history(".sky_history") { + eprintln!("Failed to save history with error: '{}'", e); + } + close_con!(con); + println!("Goodbye!"); }