1use std::ops::ControlFlow;
2
3use ghoti_exec::{ExecContext, Status};
4use owo_colors::OwoColorize;
5use rustyline::completion::Completer;
6use rustyline::error::ReadlineError;
7use rustyline::hint::Hinter;
8use rustyline::history::MemHistory;
9use rustyline::{Editor, Helper, Highlighter, Validator};
10
11#[derive(Helper, Validator, Highlighter)]
12struct ShellHelper<'a, 'ctx> {
13 ctx: &'a mut ExecContext<'ctx>,
14}
15
16impl Hinter for ShellHelper<'_, '_> {
17 type Hint = String;
18
19 fn hint(&self, line: &str, pos: usize, _ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
20 let sp_pos = line.find(' ').unwrap_or(line.len());
21 if line.is_empty() || pos > sp_pos {
22 return None;
23 }
24
25 self.ctx
26 .list_funcs_without_autoload(|name, _cmd| {
27 if let Some(rest) = name.strip_prefix(line) {
28 return ControlFlow::Break(rest.bright_black().to_string());
29 }
30 ControlFlow::Continue(())
31 })
32 .break_value()
33 }
34}
35
36impl Completer for ShellHelper<'_, '_> {
37 type Candidate = String;
38
39 fn complete(
40 &self,
41 line: &str,
42 pos: usize,
43 _ctx: &rustyline::Context<'_>,
44 ) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
45 let sp_pos = line.find(' ').unwrap_or(line.len());
46 if line.is_empty() || pos > sp_pos {
47 return Ok((0, Vec::new()));
48 }
49
50 let mut candidates = Vec::new();
51 self.ctx.list_funcs_without_autoload::<()>(|name, _cmd| {
52 if let Some(rest) = name.strip_prefix(line) {
53 candidates.push(rest.to_string());
54 }
55 ControlFlow::Continue(())
56 });
57
58 Ok((line.len(), candidates))
59 }
60}
61
62pub fn run_repl(
63 rt: &tokio::runtime::Runtime,
64 ctx: &mut ExecContext<'_>,
65) -> Result<(), Box<dyn std::error::Error>> {
66 let mut rl: Editor<ShellHelper, MemHistory> =
67 rustyline::Editor::with_history(rustyline::Config::default(), MemHistory::new())?;
68 rl.set_helper(Some(ShellHelper { ctx }));
69
70 loop {
71 let prompt = {
72 let ctx = &mut *rl.helper_mut().unwrap().ctx;
73 rt.block_on(ctx.populate_autoload_func_cache());
75
76 let st = ctx.last_status();
77 if st.is_success() {
78 "ghoti-shell> ".to_owned()
79 } else {
80 format!("ghoti-shell[{st}]> ")
81 }
82 };
83
84 let input = match rl.readline(&prompt) {
85 Ok(input) => input,
86 Err(ReadlineError::Eof) => {
87 ctx.set_last_status(Status::SUCCESS);
88 return Ok(());
89 }
90 Err(ReadlineError::Interrupted) => continue,
91 Err(err) => return Err(err.into()),
92 };
93 rl.add_history_entry(input.clone())?;
94 let ctx = &mut *rl.helper_mut().unwrap().ctx;
95
96 rt.block_on(ctx.exec_source(None, input));
97 println!();
99 }
100}