diff --git a/Cargo.lock b/Cargo.lock index 4cee1d4209..1a20ba9a9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,10 +170,25 @@ dependencies = [ ] [[package]] -name = "core-foundation-sys" -version = "0.8.2" +name = "console" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "regex", + "terminal_size", + "unicode-width", + "winapi", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "crossbeam-channel" @@ -261,7 +276,20 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9dd058f8b65922819fabb4a41e7d1964e56344042c26efbccd465202c23fa0c" dependencies = [ - "console", + "console 0.14.1", + "lazy_static", + "tempfile", + "zeroize", +] + +[[package]] +name = "dialoguer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61579ada4ec0c6031cfac3f86fdba0d195a7ebeb5e36693bd53cb5999a25beeb" +dependencies = [ + "console 0.15.0", + "fuzzy-matcher", "lazy_static", "tempfile", "zeroize", @@ -330,6 +358,7 @@ version = "0.1.0" dependencies = [ "assert_cmd", "crossterm", + "dialoguer 0.9.0", "miette", "nu-cli", "nu-command", @@ -345,6 +374,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "getrandom" version = "0.2.3" @@ -574,7 +612,7 @@ version = "0.1.0" dependencies = [ "bytesize", "chrono", - "dialoguer", + "dialoguer 0.8.0", "glob", "lscolors", "nu-engine", @@ -752,9 +790,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" [[package]] name = "predicates" @@ -797,9 +835,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.29" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" +checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70" dependencies = [ "unicode-xid", ] @@ -900,7 +938,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#6fedafffb7a783949b5e9a86149286014eddba15" +source = "git+https://github.com/nushell/reedline?branch=main#68a6ab4e5b1ada6d4e0f64bddd305ef1c852fb39" dependencies = [ "chrono", "crossterm", @@ -1092,9 +1130,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffff4a02fa61eee51f95210fc9c98ea6eeb46bb071adeafd61e1a0b9b22c6a6d" +checksum = "e223c65cd36b485a34c2ce6e38efa40777d31c4166d9076030c74cdcf971679f" dependencies = [ "cfg-if", "core-foundation-sys", @@ -1176,6 +1214,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +dependencies = [ + "once_cell", +] + [[package]] name = "time" version = "0.1.44" diff --git a/Cargo.toml b/Cargo.toml index 3d819c0621..b9294c0a5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = ["crates/nu-cli", "crates/nu-engine", "crates/nu-parser", "crates/nu-c [dependencies] reedline = { git = "https://github.com/nushell/reedline", branch = "main" } crossterm = "0.21.*" +dialoguer = { version = "0.9.0", features = ["fuzzy-select"] } nu-cli = { path="./crates/nu-cli" } nu-command = { path="./crates/nu-command" } nu-engine = { path="./crates/nu-engine" } diff --git a/crates/nu-command/src/filters/lines.rs b/crates/nu-command/src/filters/lines.rs index f3517c8e65..a6dd017a87 100644 --- a/crates/nu-command/src/filters/lines.rs +++ b/crates/nu-command/src/filters/lines.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EvaluationContext}; -use nu_protocol::{ShellError, Signature, Span, Value, ValueStream}; +use nu_protocol::{ShellError, Signature, Value, ValueStream}; pub struct Lines; @@ -28,6 +28,7 @@ impl Command for Lines { call: &Call, input: Value, ) -> Result { + let span = call.head; match input { #[allow(clippy::needless_collect)] // Collect is needed because the string may not live long enough for @@ -49,7 +50,7 @@ impl Command for Lines { Ok(Value::Stream { stream: ValueStream(Rc::new(RefCell::new(iter))), - span: Span::unknown(), + span, }) } Value::Stream { stream, span: _ } => { @@ -80,7 +81,7 @@ impl Command for Lines { Ok(Value::Stream { stream: ValueStream(Rc::new(RefCell::new(iter))), - span: Span::unknown(), + span, }) } val => Err(ShellError::UnsupportedInput( diff --git a/src/main.rs b/src/main.rs index 0ca6f3b04c..4437d5fc5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use std::{cell::RefCell, io::Write, rc::Rc}; +use dialoguer::{theme::ColorfulTheme, Select}; use miette::{IntoDiagnostic, Result}; use nu_cli::{report_error, NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; use nu_command::create_default_context; @@ -10,7 +11,7 @@ use nu_protocol::{ engine::{EngineState, EvaluationContext, Stack, StateWorkingSet}, ShellError, Value, }; -use reedline::{DefaultPrompt, Prompt}; +use reedline::{Completer, CompletionActionHandler, DefaultPrompt, LineBuffer, Prompt}; #[cfg(test)] mod tests; @@ -18,6 +19,51 @@ mod tests; // Name of environment variable where the prompt could be stored const PROMPT_COMMAND: &str = "PROMPT_COMMAND"; +struct FuzzyCompletion { + completer: Box, +} + +impl CompletionActionHandler for FuzzyCompletion { + fn handle(&mut self, present_buffer: &mut LineBuffer) { + let completions = self + .completer + .complete(present_buffer.get_buffer(), present_buffer.offset()); + + if completions.is_empty() { + // do nothing + } else if completions.len() == 1 { + let span = completions[0].0; + + let mut offset = present_buffer.offset(); + offset += completions[0].1.len() - (span.end - span.start); + + // TODO improve the support for multiline replace + present_buffer.replace(span.start..span.end, &completions[0].1); + present_buffer.set_insertion_point(offset); + } else { + let selections: Vec<_> = completions.iter().map(|(_, string)| string).collect(); + + let _ = crossterm::terminal::disable_raw_mode(); + println!(); + let result = Select::with_theme(&ColorfulTheme::default()) + .default(0) + .items(&selections[..]) + .interact() + .unwrap(); + let _ = crossterm::terminal::enable_raw_mode(); + + let span = completions[result].0; + + let mut offset = present_buffer.offset(); + offset += completions[result].1.len() - (span.end - span.start); + + // TODO improve the support for multiline replace + present_buffer.replace(span.start..span.end, &completions[result].1); + present_buffer.set_insertion_point(offset); + } + } +} + fn main() -> Result<()> { miette::set_panic_hook(); let miette_hook = std::panic::take_hook(); @@ -66,7 +112,7 @@ fn main() -> Result<()> { Ok(()) } else { - use reedline::{FileBackedHistory, ListCompletionHandler, Reedline, Signal}; + use reedline::{FileBackedHistory, Reedline, Signal}; let completer = NuCompleter::new(engine_state.clone()); let mut entry_num = 0; @@ -80,9 +126,12 @@ fn main() -> Result<()> { .with_highlighter(Box::new(NuHighlighter { engine_state: engine_state.clone(), })) - .with_completion_action_handler(Box::new( - ListCompletionHandler::default().with_completer(Box::new(completer)), - )) + .with_completion_action_handler(Box::new(FuzzyCompletion { + completer: Box::new(completer), + })) + // .with_completion_action_handler(Box::new( + // ListCompletionHandler::default().with_completer(Box::new(completer)), + // )) .with_validator(Box::new(NuValidator { engine_state: engine_state.clone(), }));