use std::{path::PathBuf, process::ExitCode};
use anyhow::{Context, Result};
use async_fs as fs;
use clap::Parser;
use directories::UserDirs;
use rustyline::{DefaultEditor, error::ReadlineError};
use lune::Runtime;
const MESSAGE_WELCOME: &str = concat!("Lune v", env!("CARGO_PKG_VERSION"));
const MESSAGE_INTERRUPT: &str = "Interrupt: ^C again to exit";
enum PromptState {
Regular,
Continuation,
}
#[derive(Debug, Clone, Default, Parser)]
pub struct ReplCommand {}
impl ReplCommand {
pub async fn run(self) -> Result<ExitCode> {
println!("{MESSAGE_WELCOME}");
let history_file_path: &PathBuf = &UserDirs::new()
.context("Failed to find user home directory")?
.home_dir()
.join(".lune_history");
if !history_file_path.exists() {
fs::write(history_file_path, &[]).await?;
}
let mut repl = DefaultEditor::new()?;
repl.load_history(history_file_path)?;
let mut interrupt_counter = 0;
let mut prompt_state = PromptState::Regular;
let mut source_code = String::new();
let mut lune_instance = Runtime::new()?;
loop {
let prompt = match prompt_state {
PromptState::Regular => "> ",
PromptState::Continuation => ">> ",
};
match repl.readline(prompt) {
Ok(code) => {
interrupt_counter = 0;
repl.add_history_entry(&code)?;
repl.save_history(history_file_path)?;
match prompt_state {
PromptState::Regular => source_code = code,
PromptState::Continuation => source_code.push_str(&code),
}
}
Err(ReadlineError::Eof) => break,
Err(ReadlineError::Interrupted) => {
interrupt_counter += 1;
if interrupt_counter == 1 {
println!("{MESSAGE_INTERRUPT}");
continue;
}
break;
}
Err(err) => {
eprintln!("REPL ERROR: {err}");
return Ok(ExitCode::FAILURE);
}
}
match lune_instance.run_custom("REPL", &source_code).await {
Ok(_) => prompt_state = PromptState::Regular,
Err(err) => {
if err.is_incomplete_input() {
prompt_state = PromptState::Continuation;
source_code.push('\n');
} else {
eprintln!("{err}");
}
}
}
}
repl.save_history(history_file_path)?;
Ok(ExitCode::SUCCESS)
}
}