From 3bb9090afae21117d01575e2c0c552ab5335e349 Mon Sep 17 00:00:00 2001 From: Jeff Date: Sun, 28 Jan 2024 12:04:33 -0500 Subject: [PATCH] Improve shell ergonomics --- src/abstract_tree/built_in_value.rs | 15 ++++++- src/built_in_functions/json.rs | 8 ++++ src/built_in_functions/mod.rs | 16 ++++++++ src/built_in_functions/string.rs | 35 +++++++++++++++++ src/main.rs | 61 +++++++++++++++++++---------- 5 files changed, 114 insertions(+), 21 deletions(-) diff --git a/src/abstract_tree/built_in_value.rs b/src/abstract_tree/built_in_value.rs index c6bffc3..91f353e 100644 --- a/src/abstract_tree/built_in_value.rs +++ b/src/abstract_tree/built_in_value.rs @@ -31,7 +31,7 @@ pub enum BuiltInValue { } impl BuiltInValue { - pub const fn name(&self) -> &'static str { + pub fn name(&self) -> &'static str { match self { BuiltInValue::Args => "args", BuiltInValue::AssertEqual => "assert_equal", @@ -44,6 +44,19 @@ impl BuiltInValue { } } + pub fn description(&self) -> &'static str { + match self { + BuiltInValue::Args => "The command line arguments sent to this program.", + BuiltInValue::AssertEqual => "Error if the two values are not equal.", + BuiltInValue::Fs => "File and directory tools.", + BuiltInValue::Json => "JSON formatting tools.", + BuiltInValue::Length => BuiltInFunction::Length.description(), + BuiltInValue::Output => "output", + BuiltInValue::Random => "random", + BuiltInValue::String => "string", + } + } + pub fn r#type(&self) -> Type { match self { BuiltInValue::Args => Type::list(Type::String), diff --git a/src/built_in_functions/json.rs b/src/built_in_functions/json.rs index f9ffa4d..fb34548 100644 --- a/src/built_in_functions/json.rs +++ b/src/built_in_functions/json.rs @@ -25,6 +25,14 @@ impl Callable for Json { } } + fn description(&self) -> &'static str { + match self { + Json::Create => "Convert a value to a JSON string.", + Json::CreatePretty => "Convert a value to a formatted JSON string.", + Json::Parse => "Convert JSON to a value", + } + } + fn r#type(&self) -> Type { match self { Json::Create => Type::function(vec![Type::Any], Type::String), diff --git a/src/built_in_functions/mod.rs b/src/built_in_functions/mod.rs index 42e49ce..3dabd86 100644 --- a/src/built_in_functions/mod.rs +++ b/src/built_in_functions/mod.rs @@ -15,6 +15,7 @@ use self::{json::Json, string::StringFunction}; pub trait Callable { fn name(&self) -> &'static str; + fn description(&self) -> &'static str; fn r#type(&self) -> Type; fn call(&self, arguments: &[Value], source: &str, outer_context: &Map) -> Result; } @@ -49,6 +50,21 @@ impl Callable for BuiltInFunction { } } + fn description(&self) -> &'static str { + match self { + BuiltInFunction::AssertEqual => "assert_equal", + BuiltInFunction::FsRead => "read", + BuiltInFunction::Json(json_function) => json_function.name(), + BuiltInFunction::Length => "length", + BuiltInFunction::Output => "output", + BuiltInFunction::RandomBoolean => "boolean", + BuiltInFunction::RandomFloat => "float", + BuiltInFunction::RandomFrom => "from", + BuiltInFunction::RandomInteger => "integer", + BuiltInFunction::String(string_function) => string_function.name(), + } + } + fn r#type(&self) -> Type { match self { BuiltInFunction::AssertEqual => Type::function(vec![Type::Any, Type::Any], Type::None), diff --git a/src/built_in_functions/string.rs b/src/built_in_functions/string.rs index 5515c9c..34217d4 100644 --- a/src/built_in_functions/string.rs +++ b/src/built_in_functions/string.rs @@ -79,6 +79,41 @@ impl Callable for StringFunction { } } + fn description(&self) -> &'static str { + match self { + StringFunction::AsBytes => "TODO", + StringFunction::EndsWith => "TODO", + StringFunction::Find => "TODO", + StringFunction::Insert => "TODO", + StringFunction::IsAscii => "TODO", + StringFunction::IsEmpty => "TODO", + StringFunction::Lines => "TODO", + StringFunction::Matches => "TODO", + StringFunction::Parse => "TODO", + StringFunction::Remove => "TODO", + StringFunction::ReplaceRange => "TODO", + StringFunction::Retain => "TODO", + StringFunction::Split => "TODO", + StringFunction::SplitAt => "TODO", + StringFunction::SplitInclusive => "TODO", + StringFunction::SplitN => "TODO", + StringFunction::SplitOnce => "TODO", + StringFunction::SplitTerminator => "TODO", + StringFunction::SplitWhitespace => "TODO", + StringFunction::StartsWith => "TODO", + StringFunction::StripPrefix => "TODO", + StringFunction::ToLowercase => "TODO", + StringFunction::ToUppercase => "TODO", + StringFunction::Trim => "TODO", + StringFunction::TrimEnd => "TODO", + StringFunction::TrimEndMatches => "TODO", + StringFunction::TrimMatches => "TODO", + StringFunction::TrimStart => "TODO", + StringFunction::TrimStartMatches => "TODO", + StringFunction::Truncate => "TODO", + } + } + fn r#type(&self) -> Type { match self { StringFunction::AsBytes => { diff --git a/src/main.rs b/src/main.rs index bc4381f..d705f8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,9 @@ use reedline::{ StyledText, Suggestion, }; -use std::{borrow::Cow, fs::read_to_string, path::PathBuf, process::Command}; +use std::{ + borrow::Cow, collections::BTreeSet, fs::read_to_string, path::PathBuf, process::Command, +}; use dust_lang::{built_in_values, Interpreter, Map, Result, Value}; @@ -123,7 +125,7 @@ impl DustHighlighter { } } -const HIGHLIGHT_TERMINATORS: [char; 8] = [' ', ':', '(', ')', '{', '}', '[', ']']; +const INPUT_TERMINATORS: [char; 8] = [' ', ':', '(', ')', '{', '}', '[', ']']; impl Highlighter for DustHighlighter { fn highlight(&self, line: &str, _cursor: usize) -> reedline::StyledText { @@ -153,14 +155,14 @@ impl Highlighter for DustHighlighter { let mut styled = StyledText::new(); - for word in line.split_inclusive(&HIGHLIGHT_TERMINATORS) { + for word in line.split_inclusive(&INPUT_TERMINATORS) { let word_is_highlighted = highlight_identifier(&mut styled, &word[0..word.len() - 1], &self.context); if word_is_highlighted { let final_char = word.chars().last().unwrap(); - if HIGHLIGHT_TERMINATORS.contains(&final_char) { + if INPUT_TERMINATORS.contains(&final_char) { let mut terminator_style = Style::new(); terminator_style.foreground = Some(Color::Cyan); @@ -246,18 +248,24 @@ impl DustCompleter { } impl Completer for DustCompleter { - fn complete(&mut self, _line: &str, pos: usize) -> Vec { - let variables = self.context.variables().unwrap(); - let mut suggestions = Vec::with_capacity(variables.len()); + fn complete(&mut self, line: &str, pos: usize) -> Vec { + let mut suggestions = Vec::new(); + let last_word = if let Some(word) = line.rsplit(INPUT_TERMINATORS).next() { + word + } else { + "" + }; - for (key, (value, r#type)) in variables.iter() { - suggestions.push(Suggestion { - value: key.clone(), - description: Some(value.to_string()), - extra: Some(vec![r#type.to_string()]), - span: Span::new(pos, pos), - append_whitespace: false, - }); + for built_in_value in built_in_values() { + if built_in_value.name().contains(last_word) { + suggestions.push(Suggestion { + value: built_in_value.name().to_string(), + description: Some(built_in_value.description().to_string()), + extra: None, + span: Span::new(pos - last_word.len(), pos), + append_whitespace: false, + }); + } } suggestions @@ -286,7 +294,7 @@ fn run_shell(context: Map) -> Result<()> { keybindings.add_binding( KeyModifiers::CONTROL, KeyCode::Char('h'), - ReedlineEvent::Menu("help menu".to_string()), + ReedlineEvent::Menu("variable menu".to_string()), ); let edit_mode = Box::new(Emacs::new(keybindings)); @@ -295,16 +303,29 @@ fn run_shell(context: Map) -> Result<()> { .expect("Error loading history."), ); let hinter = Box::new(DefaultHinter::default().with_style(Style::new().dimmed())); + let mut built_in_info = BTreeSet::new(); + + for built_in_value in built_in_values() { + built_in_info.insert(( + built_in_value.name().to_string(), + built_in_value.description().to_string(), + built_in_value.r#type().to_string(), + )); + } + let completer = DustCompleter::new(context.clone()); + let mut line_editor = Reedline::create() .with_edit_mode(edit_mode) .with_history(history) - .with_highlighter(Box::new(DustHighlighter::new(context))) + .with_highlighter(Box::new(DustHighlighter::new(context.clone()))) .with_hinter(hinter) .use_kitty_keyboard_enhancement(true) .with_completer(Box::new(completer)) .with_menu(ReedlineMenu::EngineCompleter(Box::new( - ColumnarMenu::default().with_name("help menu"), + ColumnarMenu::default() + .with_name("variable menu") + .with_text_style(Style::new().fg(Color::White)), ))); let mut prompt = StarshipPrompt::new(); @@ -333,11 +354,11 @@ fn run_shell(context: Map) -> Result<()> { } } Ok(Signal::CtrlD) | Ok(Signal::CtrlC) => { - println!("\nAborted!"); + println!("\nLeaving the Dust shell."); break; } x => { - println!("Event: {:?}", x); + println!("Unknown event: {:?}", x); } } }