use std::ops::ControlFlow;
use ghoti_exec::{ExecContext, Status};
use owo_colors::OwoColorize;
use rustyline::completion::Completer;
use rustyline::error::ReadlineError;
use rustyline::hint::Hinter;
use rustyline::history::MemHistory;
use rustyline::{Editor, Helper, Highlighter, Validator};
#[derive(Helper, Validator, Highlighter)]
struct ShellHelper<'a, 'ctx> {
ctx: &'a mut ExecContext<'ctx>,
}
impl Hinter for ShellHelper<'_, '_> {
type Hint = String;
fn hint(&self, line: &str, pos: usize, _ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
let sp_pos = line.find(' ').unwrap_or(line.len());
if line.is_empty() || pos > sp_pos {
return None;
}
self.ctx
.list_funcs_without_autoload(|name, _cmd| {
if let Some(rest) = name.strip_prefix(line) {
return ControlFlow::Break(rest.bright_black().to_string());
}
ControlFlow::Continue(())
})
.break_value()
}
}
impl Completer for ShellHelper<'_, '_> {
type Candidate = String;
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
let sp_pos = line.find(' ').unwrap_or(line.len());
if line.is_empty() || pos > sp_pos {
return Ok((0, Vec::new()));
}
let mut candidates = Vec::new();
self.ctx.list_funcs_without_autoload::<()>(|name, _cmd| {
if let Some(rest) = name.strip_prefix(line) {
candidates.push(rest.to_string());
}
ControlFlow::Continue(())
});
Ok((line.len(), candidates))
}
}
pub fn run_repl(
rt: &tokio::runtime::Runtime,
ctx: &mut ExecContext<'_>,
) -> Result<(), Box<dyn std::error::Error>> {
let mut rl: Editor<ShellHelper, MemHistory> =
rustyline::Editor::with_history(rustyline::Config::default(), MemHistory::new())?;
rl.set_helper(Some(ShellHelper { ctx }));
loop {
let prompt = {
let ctx = &mut *rl.helper_mut().unwrap().ctx;
rt.block_on(ctx.populate_autoload_func_cache());
let st = ctx.last_status();
if st.is_success() {
"ghoti-shell> ".to_owned()
} else {
format!("ghoti-shell[{st}]> ")
}
};
let input = match rl.readline(&prompt) {
Ok(input) => input,
Err(ReadlineError::Eof) => {
ctx.set_last_status(Status::SUCCESS);
return Ok(());
}
Err(ReadlineError::Interrupted) => continue,
Err(err) => return Err(err.into()),
};
rl.add_history_entry(input.clone())?;
let ctx = &mut *rl.helper_mut().unwrap().ctx;
rt.block_on(ctx.exec_source(None, input));
println!();
}
}