diff --git a/TODO.md b/TODO.md index f0112bfb54..92049f2ac8 100644 --- a/TODO.md +++ b/TODO.md @@ -18,6 +18,7 @@ - [x] ...rest without calling it rest - [x] Iteration (`each`) over tables - [x] Row conditions +- [x] Simple completions - [ ] Value serialization - [ ] Handling rows with missing columns during a cell path - [ ] Error shortcircuit (stopping on first error) diff --git a/crates/nu-cli/src/completions.rs b/crates/nu-cli/src/completions.rs new file mode 100644 index 0000000000..45b9b17bd0 --- /dev/null +++ b/crates/nu-cli/src/completions.rs @@ -0,0 +1,54 @@ +use std::{cell::RefCell, rc::Rc}; + +use nu_parser::{flatten_block, parse}; +use nu_protocol::engine::{EngineState, StateWorkingSet}; +use reedline::Completer; + +pub struct NuCompleter { + engine_state: Rc>, +} + +impl NuCompleter { + pub fn new(engine_state: Rc>) -> Self { + Self { engine_state } + } +} + +impl Completer for NuCompleter { + fn complete(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { + let engine_state = self.engine_state.borrow(); + let mut working_set = StateWorkingSet::new(&*engine_state); + let offset = working_set.next_span_start(); + let pos = offset + pos; + let (output, _err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false); + + let flattened = flatten_block(&working_set, &output); + + for flat in flattened { + if pos >= flat.0.start && pos <= flat.0.end { + match flat.1 { + nu_parser::FlatShape::External | nu_parser::FlatShape::InternalCall => { + let prefix = working_set.get_span_contents(flat.0); + let results = working_set.find_commands_by_prefix(prefix); + + return results + .into_iter() + .map(move |x| { + ( + reedline::Span { + start: flat.0.start - offset, + end: flat.0.end - offset, + }, + String::from_utf8_lossy(&x).to_string(), + ) + }) + .collect(); + } + _ => {} + } + } + } + + vec![] + } +} diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index a8c602012b..111a74c961 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -1,5 +1,7 @@ +mod completions; mod errors; mod syntax_highlight; +pub use completions::NuCompleter; pub use errors::{report_parsing_error, report_shell_error}; pub use syntax_highlight::NuHighlighter; diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 80d437a57c..a9c307fc49 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -123,6 +123,24 @@ impl EngineState { None } + pub fn find_commands_by_prefix(&self, name: &[u8]) -> Vec> { + let mut output = vec![]; + + for scope in self.scope.iter().rev() { + for decl in &scope.decls { + if decl.0.starts_with(name) { + output.push(decl.0.clone()); + } + } + } + + output + } + + pub fn get_span_contents(&self, span: Span) -> &[u8] { + &self.file_contents[span.start..span.end] + } + pub fn get_var(&self, var_id: VarId) -> &Type { self.vars .get(var_id) @@ -496,6 +514,24 @@ impl<'a> StateWorkingSet<'a> { } } + pub fn find_commands_by_prefix(&self, name: &[u8]) -> Vec> { + let mut output = vec![]; + + for scope in self.delta.scope.iter().rev() { + for decl in &scope.decls { + if decl.0.starts_with(name) { + output.push(decl.0.clone()); + } + } + } + + let mut permanent = self.permanent_state.find_commands_by_prefix(name); + + output.append(&mut permanent); + + output + } + pub fn get_block(&self, block_id: BlockId) -> &Block { let num_permanent_blocks = self.permanent_state.num_blocks(); if block_id < num_permanent_blocks { diff --git a/src/main.rs b/src/main.rs index a8015245c9..32e0fb4c6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,12 @@ -use std::{cell::RefCell, rc::Rc}; - -use nu_cli::{report_parsing_error, report_shell_error, NuHighlighter}; +use nu_cli::{report_parsing_error, report_shell_error, NuCompleter, NuHighlighter}; use nu_command::create_default_context; use nu_engine::eval_block; -use nu_parser::{flatten_block, parse}; +use nu_parser::parse; use nu_protocol::{ engine::{EngineState, EvaluationContext, StateWorkingSet}, Value, }; -use reedline::{Completer, DefaultCompletionActionHandler}; +use reedline::DefaultCompletionActionHandler; #[cfg(test)] mod tests; @@ -56,9 +54,7 @@ fn main() -> std::io::Result<()> { } else { use reedline::{DefaultPrompt, FileBackedHistory, Reedline, Signal}; - let completer = EQCompleter { - engine_state: engine_state.clone(), - }; + let completer = NuCompleter::new(engine_state.clone()); let mut line_editor = Reedline::create()? .with_history(Box::new(FileBackedHistory::with_file( @@ -153,38 +149,3 @@ fn main() -> std::io::Result<()> { Ok(()) } } - -struct EQCompleter { - engine_state: Rc>, -} - -impl Completer for EQCompleter { - fn complete(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> { - let engine_state = self.engine_state.borrow(); - let mut working_set = StateWorkingSet::new(&*engine_state); - let offset = working_set.next_span_start(); - let pos = offset + pos; - let (output, _err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false); - - let flattened = flatten_block(&working_set, &output); - - for flat in flattened { - if pos >= flat.0.start && pos <= flat.0.end { - match flat.1 { - nu_parser::FlatShape::External | nu_parser::FlatShape::InternalCall => { - return vec![( - reedline::Span { - start: flat.0.start - offset, - end: flat.0.end - offset, - }, - "hello".into(), - )] - } - _ => {} - } - } - } - - vec![] - } -}