zenyx-engine/engine/src/core/repl/input.rs

226 lines
6.4 KiB
Rust
Raw Normal View History

2024-12-05 11:00:08 -05:00
use std::{
borrow::Cow::{self, Borrowed, Owned},
2024-12-05 11:16:40 -05:00
sync::Arc,
2024-12-05 01:33:44 -06:00
};
2024-12-05 11:00:08 -05:00
use chrono::Local;
2024-12-02 15:43:39 -05:00
use colored::Colorize;
2024-12-05 11:16:40 -05:00
use parking_lot::Mutex;
2024-12-05 01:33:44 -06:00
use rustyline::{
Cmd, Completer, ConditionalEventHandler, Editor, Event, EventContext, EventHandler, Helper,
Hinter, KeyEvent, RepeatCount, Validator, completion::Completer, error::ReadlineError,
highlight::Highlighter, hint::HistoryHinter, history::DefaultHistory,
2024-12-05 01:33:44 -06:00
};
2025-03-24 19:42:32 -04:00
use tracing::{debug, error, info, warn};
2024-12-05 11:00:08 -05:00
use super::handler::COMMAND_MANAGER;
use crate::error::{Result, ZenyxError, ZenyxErrorKind};
2024-12-05 01:33:44 -06:00
struct CommandCompleter;
impl CommandCompleter {
fn new() -> Self {
CommandCompleter {}
}
}
impl Completer for CommandCompleter {
type Candidate = String;
fn complete(
&self,
line: &str,
pos: usize,
_ctx: &rustyline::Context<'_>,
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
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<String> = filtered_commands
.iter()
.filter(|(command, _)| command.starts_with(&line[..pos]))
.map(|(command, _)| command[pos..].to_string())
.collect();
println!("{:#?}", completions);
Ok((pos, completions))
}
}
2024-12-05 01:33:44 -06:00
#[derive(Completer, Helper, Hinter, Validator)]
struct MyHelper {
#[rustyline(Hinter)]
hinter: HistoryHinter,
#[rustyline(Completer)]
completer: CommandCompleter,
}
2024-12-05 01:33:44 -06:00
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> {
2024-12-06 10:56:18 -05:00
Owned(hint.italic().bright_black().to_string())
2024-12-05 01:33:44 -06:00
}
}
#[derive(Clone)]
struct BacktickEventHandler {
toggle_state: Arc<Mutex<bool>>, // Tracks whether logging is enabled or disabled
}
impl ConditionalEventHandler for BacktickEventHandler {
fn handle(&self, evt: &Event, _: RepeatCount, _: bool, _: &EventContext) -> Option<Cmd> {
if let Some(k) = evt.get(0) {
if *k == KeyEvent::from('`') {
2024-12-05 11:16:40 -05:00
let mut state = self.toggle_state.lock();
2024-12-05 11:00:08 -05:00
println!(
"Stdout Logging: {}",
if *state { "ON".green() } else { "OFF".red() }
);
2024-12-05 01:33:44 -06:00
if *state {
2025-03-24 19:42:32 -04:00
// LOGGER.write_to_stdout();
2024-12-05 01:33:44 -06:00
} else {
2025-03-24 19:42:32 -04:00
// LOGGER.write_to_file("z.log");
2024-12-05 01:33:44 -06:00
}
*state = !*state;
Some(Cmd::Noop)
} else {
None
}
} else {
unreachable!()
}
}
}
pub fn tokenize(command: &str) -> Vec<String> {
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<Vec<String>> {
let commands = input
.split(|c| c == ';' || c == '\n')
.map(|slice| slice.to_string())
.collect::<Vec<String>>();
Ok(commands)
}
pub fn evaluate_command(input: &str) -> Result<()> {
if input.trim().is_empty() {
return Ok(());
}
let commands = input
.split(|c| c == ';' || c == '\n')
.map(|slice| slice.to_string())
.collect::<Vec<String>>();
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<Vec<String>> = if tokens.len() > 1 {
Some(tokens[1..].iter().map(|s| s.to_string()).collect())
} else {
None
};
COMMAND_MANAGER
.read()
.execute(cmd_name, args)
.map_err(|e| {
ZenyxError::builder(ZenyxErrorKind::CommandExecution)
.with_message(format!("Failed to execute command: {cmd_name}"))
.with_context(format!("{e}"))
.build()
})?;
2024-12-06 16:55:49 -05:00
}
Ok(())
}
pub async fn handle_repl() -> Result<()> {
2024-12-05 01:33:44 -06:00
let mut rl = Editor::<MyHelper, DefaultHistory>::new()?;
rl.set_helper(Some(MyHelper {
hinter: HistoryHinter::new(),
completer: CommandCompleter::new(),
}));
2024-12-05 01:33:44 -06:00
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() {
2024-12-02 15:43:39 -05:00
debug!("No previous history.");
}
2024-12-05 01:33:44 -06:00
loop {
2024-12-05 01:33:44 -06:00
let time = Local::now().format("%H:%M:%S.%3f").to_string();
let prompt = format!("[{}/{}] {}", time, "SHELL", ">>\t");
let sig = rl.readline(&prompt.bright_white());
match sig {
2024-12-02 15:43:39 -05:00
Ok(line) => {
2024-12-05 01:33:44 -06:00
rl.add_history_entry(line.as_str())?;
match evaluate_command(line.as_str()) {
Ok(_) => continue,
Err(e) => error!("{e}"),
}
2024-12-02 15:43:39 -05:00
}
2024-12-05 01:33:44 -06:00
Err(ReadlineError::Interrupted) => {
2024-12-02 15:43:39 -05:00
println!("CTRL+C received, exiting...");
std::process::exit(0);
}
2024-12-05 01:33:44 -06:00
Err(ReadlineError::Eof) => {
2024-12-02 15:43:39 -05:00
println!("Error: CTRL+D pressed. Exiting...");
std::process::exit(0);
}
2024-12-02 15:43:39 -05:00
Err(err) => {
println!("Error: {}", err);
}
}
}
}