use ansi_term::Color;
use std::{borrow::Cow, collections::HashMap, fs::create_dir_all};
use directories::ProjectDirs;
use rustyline::{
completion::Completer, error::ReadlineError, highlight::Highlighter, hint::Hinter,
validate::Validator, Config, Editor, Helper,
};
use crate::{
context::{Context, VariableValue},
parser::parse,
scope::Scope,
tokenizer::tokenize,
};
struct OtomeCompleter {
definitions: HashMap<String, Vec<String>>,
}
impl Helper for OtomeCompleter {}
impl Highlighter for OtomeCompleter {
fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {
let _ = pos;
std::borrow::Cow::Borrowed(line)
}
fn highlight_prompt<'b, 's: 'b, 'p: 'b>(
&'s self,
prompt: &'p str,
_default: bool,
) -> std::borrow::Cow<'b, str> {
Cow::Owned(Color::Purple.paint(prompt).to_string())
}
fn highlight_hint<'h>(&self, hint: &'h str) -> std::borrow::Cow<'h, str> {
Cow::Owned(Color::Purple.paint(hint).to_string())
}
fn highlight_candidate<'c>(
&self,
candidate: &'c str,
_completion: rustyline::CompletionType,
) -> std::borrow::Cow<'c, str> {
Cow::Owned(Color::Purple.paint(candidate).to_string())
}
fn highlight_char(&self, line: &str, pos: usize) -> bool {
let _ = (line, pos);
false
}
}
impl Hinter for OtomeCompleter {
type Hint = String;
fn hint(&self, line: &str, _pos: usize, _ctx: &rustyline::Context<'_>) -> Option<Self::Hint> {
self.definitions.get(&line.to_string()).map(|e| {
format!(
" {}",
e.iter()
.map(|v| format!("<{v}>"))
.collect::<Vec<String>>()
.join(" ")
)
})
}
}
impl Validator for OtomeCompleter {
fn validate(
&self,
ctx: &mut rustyline::validate::ValidationContext,
) -> rustyline::Result<rustyline::validate::ValidationResult> {
let source = ctx.input();
let commands = match tokenize(source) {
Ok(tokens) => parse(tokens),
Err(e) => Err(e),
};
match commands {
Ok(_) => Ok(rustyline::validate::ValidationResult::Valid(None)),
Err(e) => {
if e.0.ends_with("end") {
Ok(rustyline::validate::ValidationResult::Incomplete)
} else {
Ok(rustyline::validate::ValidationResult::Invalid(Some(e.0)))
}
}
}
}
}
impl Completer for OtomeCompleter {
type Candidate = String;
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
if let Some(word) = line[..pos].split(" ").last() {
Ok((
pos - word.len(),
self.definitions
.iter()
.filter_map(|(n, _)| {
if n.starts_with(word) {
Some(n.clone())
} else {
None
}
})
.collect(),
))
} else {
Ok((pos, Vec::with_capacity(0)))
}
}
}
pub fn repl(context: &mut Context, scope: &mut Scope) {
let config = Config::builder().history_ignore_space(true).build();
let mut rl = Editor::<OtomeCompleter>::with_config(config).unwrap();
let proj_dirs = ProjectDirs::from("com", "jummit", "otomescript").unwrap();
let hist_file = proj_dirs.data_dir().join("history.txt");
rl.load_history(&hist_file).ok();
loop {
rl.set_helper(Some(OtomeCompleter {
definitions: scope
.functions
.clone()
.into_iter()
.map(|(a, b)| (a, b.args))
.chain(
context
.builtins
.iter()
.map(|(name, builtin)| (name.clone(), builtin.args.clone())),
)
.collect(),
}));
let line = match rl.readline("> ") {
Ok(line) => line,
Err(ReadlineError::Interrupted) => {
break;
}
Err(ReadlineError::Eof) => {
break;
}
Err(err) => {
println!("Error: {:?}", err);
break;
}
};
let commands = tokenize(&line).unwrap();
scope.commands = parse(commands).unwrap().into();
let the_res = context.evaluate(scope);
rl.add_history_entry(line);
let res = the_res.map_err(|e| (e, context.last_command.as_ref().unwrap().start));
if context.exit {
break;
}
match res {
Ok(list) => {
if !list.is_empty() {
println!(
"{}",
list.iter()
.map(|s| s.replace('\n', "\\n"))
.collect::<Vec<String>>()
.join(", ")
);
}
for var in context.get_variables(scope).iter() {
println!(
"{}: {}",
var.0,
match var.1 {
VariableValue::List(l) => {
l.join(", ")
}
VariableValue::Callable(_) => "<Callable>".to_string(),
}
)
}
if !scope.functions.is_empty() {
println!(
"Functions: {}",
scope
.functions
.iter()
.map(|(n, f)| format!("{}({})", n, f.args.join(" ")))
.collect::<Vec<String>>()
.join(", ")
)
}
}
Err(e) => println!("{} at {}:{}", e.0, e.1 .0, e.1 .1,),
}
}
create_dir_all(hist_file.parent().unwrap()).unwrap();
rl.save_history(&hist_file).unwrap();
}