use crate::shell::repl::{CONTINUATION_PROMPT, PROMPT};
use rustyline::completion::{Completer, FilenameCompleter, Pair};
use rustyline::highlight::{CmdKind, Highlighter, MatchingBracketHighlighter};
use rustyline::hint::Hinter;
use rustyline::validate::Validator;
use rustyline::{Context, Helper, Result as RustylineResult};
use std::borrow::Cow::{self, Borrowed, Owned};
use std::cell::Cell;
pub struct ShellHelper {
filename_completer: FilenameCompleter,
commands: Vec<String>,
bracket_highlighter: MatchingBracketHighlighter,
continuation: Cell<bool>,
}
impl ShellHelper {
pub fn new(commands: Vec<String>) -> Self {
Self {
filename_completer: FilenameCompleter::new(),
commands,
bracket_highlighter: MatchingBracketHighlighter::new(),
continuation: Cell::new(false),
}
}
pub fn set_continuation(&self, continuation: bool) {
self.continuation.set(continuation);
}
fn first_word_end(line: &str) -> usize {
line.find(' ').unwrap_or(line.len())
}
}
impl Completer for ShellHelper {
type Candidate = Pair;
fn complete(&self, line: &str, pos: usize, ctx: &Context<'_>) -> RustylineResult<(usize, Vec<Pair>)> {
let prefix = &line[..pos];
if !prefix.contains(' ') {
let candidates = self
.commands
.iter()
.filter(|name| name.starts_with(prefix))
.map(|name| Pair {
display: name.clone(),
replacement: name.clone(),
})
.collect();
return Ok((0, candidates));
}
self.filename_completer.complete(line, pos, ctx)
}
}
impl Hinter for ShellHelper {
type Hint = String;
}
impl Highlighter for ShellHelper {
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
let highlighted = self.bracket_highlighter.highlight(line, pos);
let first_word_end = Self::first_word_end(line);
let word = &line[..first_word_end];
if word.is_empty() || word.starts_with('!') || self.continuation.get() {
return highlighted;
}
let color = if self.commands.iter().any(|c| c == word) {
"\x1b[32m" } else {
"\x1b[31m" };
let Some(rest) = highlighted.get(first_word_end..) else {
return highlighted;
};
Owned(format!("{color}{word}\x1b[0m{rest}"))
}
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(&'s self, prompt: &'p str, _default: bool) -> Cow<'b, str> {
if prompt == PROMPT {
Owned(format!("\x1b[1;36m{prompt}\x1b[0m")) } else if prompt == CONTINUATION_PROMPT {
Owned(format!("\x1b[2m{prompt}\x1b[0m")) } else {
Borrowed(prompt)
}
}
fn highlight_char(&self, line: &str, pos: usize, kind: CmdKind) -> bool {
let bracket_changed = self.bracket_highlighter.highlight_char(line, pos, kind);
bracket_changed || (!self.continuation.get() && pos <= Self::first_word_end(line))
}
}
impl Validator for ShellHelper {}
impl Helper for ShellHelper {}