diff --git a/Cargo.lock b/Cargo.lock index 1c30bca..11a7372 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,6 +72,7 @@ version = "0.1.0" dependencies = [ "bincode", "clap", + "crossterm", "dirs", "tokio", ] @@ -170,6 +171,31 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "dirs" version = "4.0.0" @@ -190,6 +216,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -227,9 +263,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libredox" @@ -241,6 +277,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "lock_api" version = "0.4.12" @@ -251,6 +293,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.4" @@ -274,6 +322,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", + "log", "wasi", "windows-sys 0.52.0", ] @@ -360,6 +409,19 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustix" +version = "0.38.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -386,6 +448,27 @@ dependencies = [ "syn", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" diff --git a/Cargo.toml b/Cargo.toml index debd108..f18f5a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ bincode = "1.3.3" tokio = { version = "1.40.0", features = ["full"] } dirs = { version = "4.0" } clap = { version = "4.5.20", features = ["derive"] } +crossterm = "0.28.1" diff --git a/src/lib.rs b/src/lib.rs index f35e116..d226a0a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,4 +7,5 @@ pub mod util { pub mod app_data; pub mod backup; pub mod errors; + pub mod repl; } diff --git a/src/util/repl.rs b/src/util/repl.rs new file mode 100644 index 0000000..05de46f --- /dev/null +++ b/src/util/repl.rs @@ -0,0 +1,138 @@ +use crossterm::{ + cursor, + event::{self, Event, KeyCode, KeyModifiers}, + execute, + style::{Color, Print, ResetColor, SetForegroundColor}, + terminal::{self, Clear, ClearType}, +}; +use std::time::Duration; +use std::{ + error::Error, + io::{self, Write}, +}; + +use crate::common::app_builder::{AppBuilder, Arbor}; + +pub struct Repl { + arbor: Arbor, + input: String, + selected_suggestion: usize, +} + +impl Repl { + pub async fn new() -> Result> { + Ok(Self { + arbor: Arbor::build().await?, + input: "".to_string(), + selected_suggestion: 0, + }) + } + + pub async fn run(&mut self) -> Result<(), Box> { + let mut stdout = io::stdout(); + + terminal::enable_raw_mode()?; + execute!(stdout, terminal::EnterAlternateScreen)?; + + loop { + if event::poll(Duration::from_millis(100))? { + if let Event::Key(event) = event::read()? { + match event.code { + KeyCode::Char('c') if event.modifiers.contains(KeyModifiers::CONTROL) => { + break + } + KeyCode::Char(' ') => { + println!("nonono"); + } + KeyCode::Char(c) => { + self.input.push(c); + self.selected_suggestion = 0; + } + KeyCode::Backspace => { + self.input.pop(); + self.selected_suggestion = 0; + } + KeyCode::Enter => { + self.arbor + .autocomplete + .insert_word(self.input.clone()) + .await?; + self.input = "".to_string(); + self.selected_suggestion = 0; + } + KeyCode::Up => { + if self.selected_suggestion > 0 { + self.selected_suggestion -= 1; + } + } + KeyCode::Down => { + self.selected_suggestion += 1; + } + KeyCode::Tab => { + if let Some(suggestion) = self + .arbor + .autocomplete + .suggest_word(self.input.as_str()) + .await? + .get(self.selected_suggestion) + { + self.input = suggestion.to_string() + } + + self.selected_suggestion = 0; + } + KeyCode::Esc => break, + _ => panic!("Unknown keystroke!"), + } + } + } + + let suggestions = self + .arbor + .autocomplete + .suggest_word(self.input.as_str()) + .await?; + let max_index = suggestions.len().saturating_sub(1); + if self.selected_suggestion > max_index { + self.selected_suggestion = max_index; + } + + execute!(stdout, Clear(ClearType::All))?; + + execute!( + stdout, + cursor::MoveTo(0, 0), + Print(format!("> {}", self.input)) + )?; + + for (i, suggestion) in suggestions.iter().enumerate() { + if i == self.selected_suggestion { + execute!( + stdout, + cursor::MoveTo(2, (i + 1) as u16), + SetForegroundColor(Color::Green), + Print(suggestion), + ResetColor + )?; + } else { + execute!( + stdout, + cursor::MoveTo(2, (i + 1) as u16), + SetForegroundColor(Color::DarkGrey), + Print(suggestion), + ResetColor + )?; + } + } + + execute!(stdout, cursor::MoveTo(2 + self.input.len() as u16, 0))?; + + stdout.flush()?; + } + + execute!(stdout, terminal::LeaveAlternateScreen)?; + terminal::disable_raw_mode()?; + + Ok(()) + } +}