Improve shell

This commit is contained in:
Jeff 2024-01-26 21:03:54 -05:00
parent 0eee9f0936
commit a0b38d329b

View File

@ -2,10 +2,11 @@
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use crossterm::event::{KeyCode, KeyModifiers}; use crossterm::event::{KeyCode, KeyModifiers};
use nu_ansi_term::Style; use nu_ansi_term::{Color, Style};
use reedline::{ use reedline::{
default_emacs_keybindings, DefaultHinter, EditCommand, Emacs, Highlighter, Prompt, Reedline, default_emacs_keybindings, ColumnarMenu, Completer, DefaultHinter, EditCommand, Emacs,
ReedlineEvent, Signal, SqliteBackedHistory, StyledText, Highlighter, Prompt, Reedline, ReedlineEvent, ReedlineMenu, Signal, Span, SqliteBackedHistory,
StyledText, Suggestion,
}; };
use std::{borrow::Cow, fs::read_to_string, path::PathBuf, process::Command}; use std::{borrow::Cow, fs::read_to_string, path::PathBuf, process::Command};
@ -122,6 +123,8 @@ impl DustHighlighter {
} }
} }
const HIGHLIGHT_TERMINATORS: [char; 8] = [' ', ':', '(', ')', '{', '}', '[', ']'];
impl Highlighter for DustHighlighter { impl Highlighter for DustHighlighter {
fn highlight(&self, line: &str, _cursor: usize) -> reedline::StyledText { fn highlight(&self, line: &str, _cursor: usize) -> reedline::StyledText {
fn highlight_identifier(styled: &mut StyledText, word: &str, map: &Map) -> bool { fn highlight_identifier(styled: &mut StyledText, word: &str, map: &Map) -> bool {
@ -133,7 +136,7 @@ impl Highlighter for DustHighlighter {
} }
if let Value::Map(nested_map) = value { if let Value::Map(nested_map) = value {
return highlight_identifier(styled, key, nested_map); return highlight_identifier(styled, word, nested_map);
} }
} }
@ -149,17 +152,20 @@ impl Highlighter for DustHighlighter {
} }
let mut styled = StyledText::new(); let mut styled = StyledText::new();
let terminators = [' ', ':', '(', ')', '{', '}', '[', ']'];
for word in line.split_inclusive(&terminators) { for word in line.split_inclusive(&HIGHLIGHT_TERMINATORS) {
let word_is_highlighted = let word_is_highlighted =
highlight_identifier(&mut styled, &word[0..word.len() - 1], &self.context); highlight_identifier(&mut styled, &word[0..word.len() - 1], &self.context);
if word_is_highlighted { if word_is_highlighted {
let final_char = word.chars().last().unwrap(); let final_char = word.chars().last().unwrap();
if terminators.contains(&final_char) { if HIGHLIGHT_TERMINATORS.contains(&final_char) {
styled.push((Style::new(), final_char.to_string())); let mut terminator_style = Style::new();
terminator_style.foreground = Some(Color::Cyan);
styled.push((terminator_style, final_char.to_string()));
} }
} else { } else {
styled.push((Style::new(), word.to_string())); styled.push((Style::new(), word.to_string()));
@ -170,12 +176,12 @@ impl Highlighter for DustHighlighter {
} }
} }
struct DustPrompt { struct StarshipPrompt {
left: String, left: String,
right: String, right: String,
} }
impl DustPrompt { impl StarshipPrompt {
fn new() -> Self { fn new() -> Self {
Self { Self {
left: String::new(), left: String::new(),
@ -184,17 +190,27 @@ impl DustPrompt {
} }
fn reload(&mut self) { fn reload(&mut self) {
let output = Command::new("starship") let run_starship_left = Command::new("starship").arg("prompt").output();
.arg("prompt") let run_starship_right = Command::new("starship")
.output() .args(["prompt", "--right"])
.unwrap() .output();
.stdout; let left_prompt = if let Ok(output) = &run_starship_left {
String::from_utf8_lossy(&output.stdout).trim().to_string()
} else {
">".to_string()
};
let right_prompt = if let Ok(output) = &run_starship_right {
String::from_utf8_lossy(&output.stdout).trim().to_string()
} else {
">".to_string()
};
self.left = String::from_utf8_lossy(&output).trim().to_string(); self.left = left_prompt;
self.right = right_prompt;
} }
} }
impl Prompt for DustPrompt { impl Prompt for StarshipPrompt {
fn render_prompt_left(&self) -> Cow<str> { fn render_prompt_left(&self) -> Cow<str> {
Cow::Borrowed(&self.left) Cow::Borrowed(&self.left)
} }
@ -204,7 +220,7 @@ impl Prompt for DustPrompt {
} }
fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow<str> { fn render_prompt_indicator(&self, _prompt_mode: reedline::PromptEditMode) -> Cow<str> {
Cow::Borrowed("") Cow::Borrowed(" ")
} }
fn render_prompt_multiline_indicator(&self) -> Cow<str> { fn render_prompt_multiline_indicator(&self) -> Cow<str> {
@ -219,6 +235,35 @@ impl Prompt for DustPrompt {
} }
} }
pub struct DustCompleter {
context: Map,
}
impl DustCompleter {
fn new(context: Map) -> Self {
DustCompleter { context }
}
}
impl Completer for DustCompleter {
fn complete(&mut self, _line: &str, pos: usize) -> Vec<Suggestion> {
let variables = self.context.variables().unwrap();
let mut suggestions = Vec::with_capacity(variables.len());
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,
});
}
suggestions
}
}
fn run_shell(context: Map) -> Result<()> { fn run_shell(context: Map) -> Result<()> {
let mut interpreter = Interpreter::new(context.clone()); let mut interpreter = Interpreter::new(context.clone());
let mut keybindings = default_emacs_keybindings(); let mut keybindings = default_emacs_keybindings();
@ -231,30 +276,37 @@ fn run_shell(context: Map) -> Result<()> {
keybindings.add_binding( keybindings.add_binding(
KeyModifiers::NONE, KeyModifiers::NONE,
KeyCode::Enter, KeyCode::Enter,
ReedlineEvent::Multiple(vec![
ReedlineEvent::SubmitOrNewline, ReedlineEvent::SubmitOrNewline,
ReedlineEvent::ExecuteHostCommand("output('hi')".to_string()),
]),
); );
keybindings.add_binding( keybindings.add_binding(
KeyModifiers::NONE, KeyModifiers::NONE,
KeyCode::Tab, KeyCode::Tab,
ReedlineEvent::Edit(vec![EditCommand::InsertString(" ".to_string())]), ReedlineEvent::Edit(vec![EditCommand::InsertString(" ".to_string())]),
); );
keybindings.add_binding(
KeyModifiers::CONTROL,
KeyCode::Char('h'),
ReedlineEvent::Menu("help menu".to_string()),
);
let edit_mode = Box::new(Emacs::new(keybindings)); let edit_mode = Box::new(Emacs::new(keybindings));
let history = Box::new( let history = Box::new(
SqliteBackedHistory::with_file(PathBuf::from("target/history"), None, None) SqliteBackedHistory::with_file(PathBuf::from("target/history"), None, None)
.expect("Error loading history."), .expect("Error loading history."),
); );
let hinter = Box::new(DefaultHinter::default()); let hinter = Box::new(DefaultHinter::default().with_style(Style::new().dimmed()));
let completer = DustCompleter::new(context.clone());
let mut line_editor = Reedline::create() let mut line_editor = Reedline::create()
.with_edit_mode(edit_mode) .with_edit_mode(edit_mode)
.with_history(history) .with_history(history)
.with_highlighter(Box::new(DustHighlighter::new(context))) .with_highlighter(Box::new(DustHighlighter::new(context)))
.with_hinter(hinter) .with_hinter(hinter)
.use_kitty_keyboard_enhancement(true); .use_kitty_keyboard_enhancement(true)
let mut prompt = DustPrompt::new(); .with_completer(Box::new(completer))
.with_menu(ReedlineMenu::EngineCompleter(Box::new(
ColumnarMenu::default().with_name("help menu"),
)));
let mut prompt = StarshipPrompt::new();
prompt.reload(); prompt.reload();