use std::{ borrow::Cow::{self, Borrowed, Owned}, sync::Arc, time::{SystemTime, UNIX_EPOCH}, }; use colored::Colorize; use parking_lot::Mutex; use rustyline::{ Cmd, Completer, ConditionalEventHandler, Editor, Event, EventContext, EventHandler, Helper, Hinter, KeyEvent, RepeatCount, Validator, completion::Completer, error::ReadlineError, highlight::Highlighter, hint::HistoryHinter, history::DefaultHistory, }; #[allow(unused_imports)] use tracing::{debug, error, info, warn}; use super::handler::COMMAND_MANAGER; use crate::error::{Result, ZenyxError, ZenyxErrorKind}; #[derive(Default)] struct CommandCompleter; impl Completer for CommandCompleter { type Candidate = String; fn complete( &self, line: &str, pos: usize, _ctx: &rustyline::Context<'_>, ) -> rustyline::Result<(usize, Vec)> { let binding = COMMAND_MANAGER.read(); let binding = binding.get_commands(); let filtered_commands: Vec<_> = binding .filter(|(command, _)| command.starts_with(line)) .collect(); let completions: Vec = filtered_commands .iter() .filter(|(command, _)| command.starts_with(&line[..pos])) .map(|(command, _)| command[pos..].to_string()) .collect(); println!("{:#?}", completions); Ok((pos, completions)) } } #[derive(Completer, Helper, Hinter, Validator)] struct MyHelper { #[rustyline(Hinter)] hinter: HistoryHinter, #[rustyline(Completer)] completer: CommandCompleter, } impl Highlighter for MyHelper { fn highlight_prompt<'b, 's: 'b, 'p: 'b>( &'s self, prompt: &'p str, default: bool, ) -> Cow<'b, str> { if default { Owned(prompt.bright_black().bold().to_string()) } else { Borrowed(prompt) } } fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { Owned(hint.italic().bright_black().to_string()) } } #[derive(Clone)] struct BacktickEventHandler { toggle_state: Arc>, // Tracks whether logging is enabled or disabled } impl ConditionalEventHandler for BacktickEventHandler { fn handle(&self, evt: &Event, _: RepeatCount, _: bool, _: &EventContext) -> Option { if let Some(k) = evt.get(0) { if *k == KeyEvent::from('`') { let mut state = self.toggle_state.lock(); println!( "Stdout Logging: {}", if *state { "ON".green() } else { "OFF".red() } ); if *state { // LOGGER.write_to_stdout(); } else { // LOGGER.write_to_file("z.log"); } *state = !*state; Some(Cmd::Noop) } else { None } } else { unreachable!() } } } pub fn tokenize(command: &str) -> Vec { let mut tokens = Vec::new(); let mut current_token = String::new(); let mut inside_string = false; for char in command.chars() { if char == '"' || char == '\'' { inside_string = !inside_string; } else if char.is_whitespace() && !inside_string { if !current_token.is_empty() { tokens.push(current_token); current_token = String::new(); } } else { current_token.push(char); } } // ignore the last token if it's empty. Who are we. Mojang? - Caz if !current_token.is_empty() { tokens.push(current_token); } tokens } pub fn parse_command(input: &str) -> Result> { let commands = input .split(|c| c == ';' || c == '\n') .map(|slice| slice.to_string()) .collect::>(); Ok(commands) } pub fn evaluate_command(input: &str) -> std::result::Result { if input.trim().is_empty() { let err = ZenyxError::builder(ZenyxErrorKind::CommandParsing) .with_message("Input was empty") .build(); return Err(err); } let commands = input .split(|c| c == ';' || c == '\n') .map(|slice| slice.to_string()) .collect::>(); let mut output = String::new(); for command in commands { let command = command.trim(); if command.is_empty() { error!("Empty command, skipping."); continue; } let tokens = tokenize(command); if tokens.is_empty() { error!("Empty command, skipping."); continue; } let cmd_name = &tokens[0]; let args: Option> = if tokens.len() > 1 { Some(tokens[1..].iter().map(|s| s.to_string()).collect()) } else { None }; match COMMAND_MANAGER.read().execute(cmd_name, args) { Ok(command_output) => output.push_str(&command_output), Err(e) => { return Err(e); } } } Ok(output) } pub fn format_time() -> String { let now = SystemTime::now(); let duration = now.duration_since(UNIX_EPOCH).unwrap(); let total_seconds = duration.as_secs(); let nanos = duration.subsec_nanos(); let milliseconds = nanos / 1_000_000; let seconds_since_midnight_utc = total_seconds % (24 * 3600); let hour = (seconds_since_midnight_utc / 3600) % 24; let minute = (seconds_since_midnight_utc / 60) % 60; let second = seconds_since_midnight_utc % 60; format!( "{:02}:{:02}:{:02}.{:03}", hour, minute, second, milliseconds ) } pub async fn handle_repl() -> Result<()> { let mut rl = Editor::::new()?; rl.set_helper(Some(MyHelper { hinter: HistoryHinter::new(), completer: CommandCompleter::default(), })); rl.bind_sequence( KeyEvent::from('`'), EventHandler::Conditional(Box::new(BacktickEventHandler { toggle_state: Arc::new(Mutex::new(false)), })), ); if rl.load_history("history.txt").is_err() { debug!("No previous history."); } loop { let time = format_time(); let prompt = format!("[{}/{}] {}", time, "SHELL", ">>\t"); let sig = rl.readline(&prompt.bright_white()); match sig { Ok(line) => { rl.add_history_entry(line.as_str())?; match evaluate_command(line.as_str()) { Ok(_) => continue, Err(e) => error!("{e}"), } } Err(ReadlineError::Interrupted) => { println!("CTRL+C received, exiting..."); std::process::exit(0); } Err(ReadlineError::Eof) => { println!("Error: CTRL+D pressed. Exiting..."); std::process::exit(0); } Err(err) => { println!("Error: {}", err); } } } }