diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a40e70c..ccc11ae 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,7 +1,7 @@ use std::{fmt::Write as _, process::ExitCode}; use anyhow::{Context, Result}; -use clap::{CommandFactory, Parser}; +use clap::Parser; use lune::Lune; use tokio::{ @@ -147,14 +147,9 @@ impl Cli { if generate_file_requested { return Ok(ExitCode::SUCCESS); } - - // HACK: We know that we didn't get any arguments here but since - // script_path is optional clap will not error on its own, to fix - // we will duplicate the CLI command and fetch the version of - // lune to display - let exit_code_status = repl::show_interface(Cli::command()).await; - - return exit_code_status; + // If we did not generate any typedefs we know that the user did not + // provide any other options, and in that case we should enter the REPL + return repl::show_interface().await; } // Figure out if we should read from stdin or from a file, // reading from stdin is marked by passing a single "-" diff --git a/src/cli/repl.rs b/src/cli/repl.rs index 3991592..6468cc0 100644 --- a/src/cli/repl.rs +++ b/src/cli/repl.rs @@ -1,42 +1,40 @@ use std::{path::PathBuf, process::ExitCode}; -use anyhow::{Error, Result}; -use clap::Command; +use anyhow::{Context, Result}; use directories::UserDirs; -use lune::Lune; use rustyline::{error::ReadlineError, DefaultEditor}; +use lune::Lune; + +const MESSAGE_WELCOME: &str = concat!("Lune v", env!("CARGO_PKG_VERSION")); +const MESSAGE_INTERRUPT: &str = "Interrupt: ^C again to exit"; + #[derive(PartialEq)] enum PromptState { Regular, Continuation, } -// Isn't dependency injection plain awesome?! -pub async fn show_interface(cmd: Command) -> Result { - let lune_version = cmd.get_version(); +pub async fn show_interface() -> Result { + println!("{MESSAGE_WELCOME}"); - // The version is mandatory and will always exist - println!("Lune v{}", lune_version.unwrap()); - - let lune_instance = Lune::new(); - - let mut repl = DefaultEditor::new()?; let history_file_path: &PathBuf = &UserDirs::new() - .ok_or(Error::msg("cannot find user home directory"))? + .context("Failed to find user home directory")? .home_dir() .join(".lune_history"); - if !history_file_path.exists() { - std::fs::write(&history_file_path, String::new())?; + tokio::fs::write(history_file_path, &[]).await?; } + let mut repl = DefaultEditor::new()?; repl.load_history(history_file_path)?; - let mut interrupt_counter = 0u32; - let mut prompt_state: PromptState = PromptState::Regular; + let mut interrupt_counter = 0; + let mut prompt_state = PromptState::Regular; let mut source_code = String::new(); + let lune_instance = Lune::new(); + loop { let prompt = match prompt_state { PromptState::Regular => "> ", @@ -45,43 +43,43 @@ pub async fn show_interface(cmd: Command) -> Result { match repl.readline(prompt) { Ok(code) => { - if prompt_state == PromptState::Continuation { - source_code.push_str(&code); - } else if prompt_state == PromptState::Regular { - source_code = code.clone(); - } - - repl.add_history_entry(code.as_str())?; - - // If source code eval was requested, we reset the counter interrupt_counter = 0; - } - Err(ReadlineError::Interrupted) => { - // HACK: We actually want the user to do ^C twice to exit, - // but the user would need to ^C one more time even after - // the check passes, so we check for 1 instead of 2 - if interrupt_counter != 1 { - println!("Interrupt: ^C again to exit"); + // TODO: Should we add history entries for each separate line? + // Or should we add and save history only when we have complete + // lua input that may or may not be multiple lines long? + repl.add_history_entry(&code)?; + repl.save_history(history_file_path)?; - // Increment the counter - interrupt_counter += 1; - } else { - repl.save_history(history_file_path)?; - break; + match prompt_state { + PromptState::Regular => source_code = code, + PromptState::Continuation => source_code.push_str(&code), } } - Err(ReadlineError::Eof) => { - repl.save_history(history_file_path)?; + + Err(ReadlineError::Eof) => break, + Err(ReadlineError::Interrupted) => { + interrupt_counter += 1; + + // NOTE: We actually want the user to do ^C twice to exit, + // and if we get an interrupt we should continue to the next + // readline loop iteration so we don't run input code twice + if interrupt_counter == 1 { + println!("{MESSAGE_INTERRUPT}"); + continue; + } + break; } + Err(err) => { eprintln!("REPL ERROR: {err}"); return Ok(ExitCode::FAILURE); } }; - let eval_result = lune_instance.run("REPL", source_code.clone()).await; + // TODO: Preserve context here somehow? + let eval_result = lune_instance.run("REPL", &source_code).await; match eval_result { Ok(_) => prompt_state = PromptState::Regular, @@ -89,7 +87,7 @@ pub async fn show_interface(cmd: Command) -> Result { Err(err) => { if err.is_incomplete_input() { prompt_state = PromptState::Continuation; - source_code.push('\n') + source_code.push('\n'); } else { eprintln!("{err}"); } @@ -97,5 +95,7 @@ pub async fn show_interface(cmd: Command) -> Result { }; } + repl.save_history(history_file_path)?; + Ok(ExitCode::SUCCESS) }