Replace reedline with rustyline

This commit is contained in:
Jeff 2023-08-28 16:14:55 -04:00
parent 3cc62129c7
commit e528c09623
3 changed files with 213 additions and 292 deletions

165
Cargo.lock generated
View File

@ -68,7 +68,7 @@ checksum = "9eac0a7f2d7cd7a93b938af401d3d8e8b7094217989a7c25c55a953023436e31"
dependencies = [
"accesskit",
"accesskit_consumer",
"arrayvec 0.7.4",
"arrayvec",
"once_cell",
"paste",
"windows 0.48.0",
@ -233,12 +233,6 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "arrayvec"
version = "0.7.4"
@ -710,8 +704,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ab77dbd8adecaf3f0db40581631b995f312a8a5ae3aa9993188bb8f23d83a5b"
dependencies = [
"crossterm",
"strum 0.24.1",
"strum_macros 0.24.3",
"strum",
"strum_macros",
"unicode-width",
]
@ -836,7 +830,6 @@ dependencies = [
"libc",
"mio",
"parking_lot",
"serde",
"signal-hook",
"signal-hook-mio",
"winapi",
@ -936,11 +929,10 @@ dependencies = [
"egui_extras",
"git2",
"json",
"nu-ansi-term",
"rand",
"rayon",
"reedline",
"reqwest",
"rustyline",
"serde",
"serde_json",
"sysinfo",
@ -1065,6 +1057,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "enumflags2"
version = "0.7.7"
@ -1649,15 +1647,6 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.9"
@ -1948,6 +1937,15 @@ dependencies = [
"jni-sys",
]
[[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.24.3"
@ -2010,15 +2008,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "nu-ansi-term"
version = "0.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "num-integer"
version = "0.1.45"
@ -2365,6 +2354,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.5"
@ -2432,26 +2431,6 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "reedline"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2fde955d11817fdcb79d703932fb6b473192cb36b6a92ba21f7f4ac0513374e"
dependencies = [
"chrono",
"crossterm",
"fd-lock",
"itertools",
"nu-ansi-term",
"serde",
"strip-ansi-escapes",
"strum 0.25.0",
"strum_macros 0.25.2",
"thiserror",
"unicode-segmentation",
"unicode-width",
]
[[package]]
name = "regex"
version = "1.9.4"
@ -2557,6 +2536,41 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]]
name = "rustyline"
version = "12.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9"
dependencies = [
"bitflags 2.4.0",
"cfg-if",
"clipboard-win",
"fd-lock",
"home",
"libc",
"log",
"memchr",
"nix 0.26.3",
"radix_trie",
"rustyline-derive",
"scopeguard",
"unicode-segmentation",
"unicode-width",
"utf8parse",
"winapi",
]
[[package]]
name = "rustyline-derive"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a32af5427251d2e4be14fc151eabe18abb4a7aad5efee7044da9f096c906a43"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
]
[[package]]
name = "ryu"
version = "1.0.15"
@ -2830,15 +2844,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
[[package]]
name = "strip-ansi-escapes"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8"
dependencies = [
"vte",
]
[[package]]
name = "strsim"
version = "0.10.0"
@ -2851,12 +2856,6 @@ version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
[[package]]
name = "strum"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
[[package]]
name = "strum_macros"
version = "0.24.3"
@ -2870,19 +2869,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "strum_macros"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.29",
]
[[package]]
name = "syn"
version = "1.0.109"
@ -2971,7 +2957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67"
dependencies = [
"arrayref",
"arrayvec 0.7.4",
"arrayvec",
"bytemuck",
"cfg-if",
"png",
@ -3228,27 +3214,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vte"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
dependencies = [
"arrayvec 0.5.2",
"utf8parse",
"vte_generate_state_changes",
]
[[package]]
name = "vte_generate_state_changes"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "waker-fn"
version = "1.1.0"

View File

@ -27,12 +27,11 @@ sysinfo = "0.29.6"
toml = "0.7.6"
toml_edit = "0.19.14"
comfy-table = "7.0.1"
reedline = "0.22.0"
clap = { version = "4.3.19", features = ["derive"] }
nu-ansi-term = "0.49"
git2 = "0.17.2"
csv = "1.2.2"
json = "0.12.4"
reqwest = { version = "0.11.18", features = ["blocking", "json"] }
serde_json = "1.0.104"
egui_extras = "0.22.0"
rustyline = { version = "12.0.0", features = ["with-file-history", "derive"] }

View File

@ -1,13 +1,19 @@
//! Command line interface for the whale programming language.
use clap::Parser;
use nu_ansi_term::{Color, Style};
use reedline::{
default_emacs_keybindings, ColumnarMenu, Completer, DefaultHinter, DefaultPrompt,
DefaultPromptSegment, EditCommand, Emacs, FileBackedHistory, KeyCode, KeyModifiers, Reedline,
ReedlineEvent, ReedlineMenu, Signal, Span, Suggestion,
use eframe::epaint::ahash::HashSet;
use rustyline::{
completion::FilenameCompleter,
error::ReadlineError,
highlight::Highlighter,
hint::{Hint, Hinter, HistoryHinter},
history::DefaultHistory,
validate::MatchingBracketValidator,
Completer, Context, DefaultEditor, Editor, Helper, Hinter, Validator,
};
use std::{
borrow::Cow,
collections::BTreeSet,
fs::{self, read_to_string},
path::PathBuf,
};
@ -49,205 +55,156 @@ fn main() {
}
}
#[derive(Helper, Completer, Validator)]
struct DustReadline {
#[rustyline(Completer)]
completer: FilenameCompleter,
tool_hints: Vec<ToolHint>,
#[rustyline(Validator)]
validator: MatchingBracketValidator,
#[rustyline(Hinter)]
hinter: HistoryHinter,
}
impl DustReadline {
fn new() -> Self {
Self {
completer: FilenameCompleter::new(),
validator: MatchingBracketValidator::new(),
hinter: HistoryHinter {},
tool_hints: TOOL_LIST
.iter()
.map(|tool| ToolHint {
display: tool.info().identifier.to_string(),
complete_to: tool.info().identifier.len(),
})
.collect(),
}
}
}
struct ToolHint {
display: String,
complete_to: usize,
}
impl Hint for ToolHint {
fn display(&self) -> &str {
&self.display
}
fn completion(&self) -> Option<&str> {
if self.complete_to > 0 {
Some(&self.display[..self.complete_to])
} else {
None
}
}
}
impl ToolHint {
fn suffix(&self, strip_chars: usize) -> ToolHint {
ToolHint {
display: self.display[strip_chars..].to_string(),
complete_to: self.complete_to.saturating_sub(strip_chars),
}
}
}
impl Hinter for DustReadline {
type Hint = ToolHint;
fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option<Self::Hint> {
if line.is_empty() || pos < line.len() {
return None;
}
self.tool_hints.iter().find_map(|tool_hint| {
if tool_hint.display.starts_with(line) {
Some(tool_hint.suffix(pos))
} else {
None
}
})
}
}
impl Highlighter for DustReadline {
fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {
let _ = pos;
Cow::Borrowed(line)
}
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
default: bool,
) -> std::borrow::Cow<'b, str> {
let _ = default;
Cow::Borrowed(prompt)
}
fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {
Cow::Borrowed(hint)
}
fn highlight_candidate<'c>(
&self,
candidate: &'c str, // FIXME should be Completer::Candidate
completion: rustyline::CompletionType,
) -> std::borrow::Cow<'c, str> {
let _ = completion;
Cow::Borrowed(candidate)
}
fn highlight_char(&self, line: &str, pos: usize) -> bool {
let _ = (line, pos);
false
}
}
fn run_cli_shell() {
let mut context = VariableMap::new();
let mut line_editor = setup_reedline();
let prompt = DefaultPrompt {
left_prompt: DefaultPromptSegment::WorkingDirectory,
right_prompt: DefaultPromptSegment::CurrentDateTime,
};
let mut rl: Editor<DustReadline, DefaultHistory> = Editor::new().unwrap();
rl.set_helper(Some(DustReadline::new()));
if rl.load_history("target/history.txt").is_err() {
println!("No previous history.");
}
loop {
let sig = line_editor.read_line(&prompt);
let readline = rl.readline(">> ");
match readline {
Ok(line) => {
let line = line.as_str();
match sig {
Ok(Signal::Success(buffer)) => {
let eval_result = eval_with_context(&buffer, &mut context);
rl.add_history_entry(line).unwrap();
let eval_result = eval_with_context(line, &mut context);
match eval_result {
Ok(value) => println!("{value}"),
Err(error) => eprintln!("{error}"),
}
}
Ok(Signal::CtrlD) | Ok(Signal::CtrlC) => {
println!("\nExit");
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
break;
}
signal => {
println!("Unhandled signal: {:?}", signal);
Err(ReadlineError::Eof) => {
println!("CTRL-D");
break;
}
Err(err) => {
println!("Error: {:?}", err);
break;
}
}
}
}
struct WhaleCompeleter {
macro_list: Vec<Suggestion>,
files: Vec<Suggestion>,
}
impl WhaleCompeleter {
pub fn new() -> Self {
WhaleCompeleter {
macro_list: Vec::new(),
files: Vec::new(),
}
}
pub fn set_command_list(&mut self, macro_list: Vec<&'static dyn Tool>) -> &mut Self {
self.macro_list = macro_list
.iter()
.map(|r#macro| {
let ToolInfo {
identifier,
description,
group,
inputs,
} = r#macro.info();
let description = format!("{description} | {group}");
let inputs = inputs
.iter()
.map(|value_type| value_type.to_string())
.collect();
Suggestion {
value: identifier.to_string() + "()",
description: Some(description),
extra: Some(inputs),
..Default::default()
}
})
.collect();
self.macro_list
.sort_by_key(|suggestion| suggestion.extra.clone());
self
}
pub fn get_suggestions(&mut self, start: usize, end: usize) -> Vec<Suggestion> {
let macro_suggestions = self
.macro_list
.iter()
.cloned()
.map(|suggestion| Suggestion {
span: Span { start, end },
..suggestion
});
let file_suggestions = self.files.iter().cloned().map(|suggestion| Suggestion {
span: Span { start, end },
..suggestion
});
file_suggestions.chain(macro_suggestions).collect()
}
pub fn update_files(&mut self, mut path: &str) {
if path.starts_with('\"') {
path = &path[1..];
}
let path = PathBuf::from(path);
if !path.is_dir() {
return;
}
self.files = fs::read_dir(path)
.unwrap()
.map(|entry| {
let path = entry.unwrap().path();
let path = path.to_string_lossy();
Suggestion {
value: format!("\"{path}\""),
description: None,
..Default::default()
}
})
.collect();
}
}
impl Completer for WhaleCompeleter {
fn complete(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
let split = line.split(' ');
let current_word = split.last().unwrap_or("");
let start = pos.saturating_sub(current_word.len());
let end = line.len();
self.update_files(current_word);
self.get_suggestions(start, end)
}
}
fn setup_reedline() -> Reedline {
let mut completer = Box::new(WhaleCompeleter::new());
completer.set_command_list(TOOL_LIST.to_vec());
let completion_menu = Box::new(
ColumnarMenu::default()
.with_name("completion_menu")
.with_columns(1)
.with_text_style(Style {
foreground: Some(Color::White),
is_dimmed: false,
..Default::default()
})
.with_description_text_style(Style {
is_dimmed: true,
..Default::default()
})
.with_selected_text_style(Style {
is_bold: true,
background: Some(Color::Black),
foreground: Some(Color::White),
..Default::default()
}),
);
let mut keybindings = default_emacs_keybindings();
keybindings.add_binding(
KeyModifiers::NONE,
KeyCode::Tab,
ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("completion_menu".to_string()),
ReedlineEvent::MenuNext,
]),
);
keybindings.add_binding(
KeyModifiers::SHIFT,
KeyCode::Tab,
ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("completion_menu".to_string()),
ReedlineEvent::MenuPrevious,
]),
);
keybindings.add_binding(
KeyModifiers::ALT,
KeyCode::Enter,
ReedlineEvent::Edit(vec![EditCommand::InsertNewline]),
);
let edit_mode = Box::new(Emacs::new(keybindings));
let history = Box::new(
FileBackedHistory::with_file(100, "target/history.txt".into())
.expect("Error configuring shell history file."),
);
let mut hinter = DefaultHinter::default();
hinter = hinter.with_style(Style {
foreground: Some(Color::Yellow),
..Default::default()
});
Reedline::create()
.with_completer(completer)
.with_menu(ReedlineMenu::EngineCompleter(completion_menu))
.with_edit_mode(edit_mode)
.with_history(history)
.with_hinter(Box::new(hinter))
.with_partial_completions(true)
.with_quick_completions(true)
rl.save_history("target/history.txt").unwrap();
}