use url::Url; use tracing_subscriber::EnvFilter; use circle_client_ws::CircleWsClient; use rustyline::error::ReadlineError; // Remove direct History import, DefaultEditor handles it. use rustyline::{DefaultEditor, Config, EditMode}; use std::fs; use std::process::Command; use std::env; use tempfile::Builder as TempFileBuilder; // Use Builder for suffix // std::io::Write is not used if we don't pre-populate temp_file // use std::io::Write; async fn execute_script(client: &mut CircleWsClient, script_content: String) { if script_content.trim().is_empty() { println!("Script is empty, not sending."); return; } println!("Sending script to server:\n---\n{}\n---", script_content); match client.play(script_content).await { Ok(play_result) => { println!("server: {}", play_result.output); } Err(e) => { eprintln!("Error executing script: {}", e); if matches!(e, circle_client_ws::CircleWsClientError::NotConnected | circle_client_ws::CircleWsClientError::ConnectionError(_)) { eprintln!("Connection lost. You may need to restart the REPL and reconnect."); // Optionally, could attempt to trigger a full exit here or set a flag } } } } async fn run_repl(ws_url_str: String) -> Result<(), Box> { println!("Attempting to connect to {}...", ws_url_str); let mut client = CircleWsClient::new(ws_url_str.clone()); match client.connect().await { Ok(_) => { println!("Connected to {}!", ws_url_str); println!("Type Rhai scripts, '.edit' to use $EDITOR, '.run ' to execute a file, or 'exit'/'quit'."); println!("Vi mode enabled for input line."); } Err(e) => { return Err(format!("Failed to connect: {}", e).into()); } } let config = Config::builder() .edit_mode(EditMode::Vi) .auto_add_history(true) // Automatically add to history .build(); let mut rl = DefaultEditor::with_config(config)?; let history_file = ".rhai_repl_history.txt"; // Simple history file in current dir if rl.load_history(history_file).is_err() { // No history found or error loading, not critical } let prompt = format!("rhai ({})> ", ws_url_str); loop { let readline = rl.readline(&prompt); match readline { Ok(line) => { let input = line.trim(); if input.eq_ignore_ascii_case("exit") || input.eq_ignore_ascii_case("quit") { println!("Exiting REPL."); break; } else if input.eq_ignore_ascii_case(".edit") { // Correct way to create a temp file with a suffix let temp_file = TempFileBuilder::new() .prefix("rhai_script_") // Optional: add a prefix .suffix(".rhai") .tempfile_in(".") // Create in current directory for simplicity .map_err(|e| format!("Failed to create temp file: {}", e))?; // You can pre-populate the temp file if needed: // use std::io::Write; // Add this import if using write_all // if let Err(e) = temp_file.as_file().write_all(b"// Start your Rhai script here\n") { // eprintln!("Failed to write initial content to temp file: {}", e); // } let temp_path = temp_file.path().to_path_buf(); let editor_cmd_str = env::var("EDITOR").unwrap_or_else(|_| "vi".to_string()); let mut editor_parts = editor_cmd_str.split_whitespace(); let editor_executable = editor_parts.next().unwrap_or("vi"); // Default to vi if $EDITOR is empty string let editor_args: Vec<&str> = editor_parts.collect(); println!("Launching editor: '{}' with args: {:?} for script editing. Save and exit editor to execute.", editor_executable, editor_args); let mut command = Command::new(editor_executable); command.args(editor_args); // Add any arguments from $EDITOR (like -w) command.arg(&temp_path); // Add the temp file path as the last argument let status = command.status(); match status { Ok(exit_status) if exit_status.success() => { match fs::read_to_string(&temp_path) { Ok(script_content) => { execute_script(&mut client, script_content).await; } Err(e) => eprintln!("Error reading temp file {:?}: {}", temp_path, e), } } Ok(exit_status) => eprintln!("Editor exited with status: {}. Script not executed.", exit_status), Err(e) => eprintln!("Failed to launch editor '{}': {}. Ensure it's in your PATH.", editor_executable, e), // Changed 'editor' to 'editor_executable' } // temp_file is automatically deleted when it goes out of scope } else if input.starts_with(".run ") || input.starts_with("run ") { let parts: Vec<&str> = input.splitn(2, ' ').collect(); if parts.len() == 2 { let file_path = parts[1]; println!("Attempting to run script from file: {}", file_path); match fs::read_to_string(file_path) { Ok(script_content) => { execute_script(&mut client, script_content).await; } Err(e) => eprintln!("Error reading file {}: {}", file_path, e), } } else { eprintln!("Usage: .run "); } } else if !input.is_empty() { execute_script(&mut client, input.to_string()).await; } // rl.add_history_entry(line.as_str()) is handled by auto_add_history(true) } Err(ReadlineError::Interrupted) => { // Ctrl-C println!("Input interrupted. Type 'exit' or 'quit' to close."); continue; } Err(ReadlineError::Eof) => { // Ctrl-D println!("Exiting REPL (EOF)."); break; } Err(err) => { eprintln!("Error reading input: {:?}", err); break; } } } if rl.save_history(history_file).is_err() { // Failed to save history, not critical } client.disconnect().await; println!("Disconnected."); Ok(()) } #[tokio::main] async fn main() { tracing_subscriber::fmt() .with_env_filter(EnvFilter::from_default_env().add_directive("rhai_repl_cli=info".parse().unwrap()).add_directive("circle_client_ws=info".parse().unwrap())) .init(); let args: Vec = env::args().collect(); let ws_url_str = if args.len() > 1 { args[1].clone() } else { let default_url = "ws://127.0.0.1:8081/ws".to_string(); // Default to first circle println!("No WebSocket URL provided. Defaulting to: {}", default_url); println!("You can also provide a URL as a command line argument, e.g.: cargo run ws://127.0.0.1:8082/ws"); default_url }; match Url::parse(&ws_url_str) { Ok(parsed_url) => { if parsed_url.scheme() != "ws" && parsed_url.scheme() != "wss" { eprintln!("Invalid WebSocket URL scheme: {}. Must be 'ws' or 'wss'.", parsed_url.scheme()); return; } if let Err(e) = run_repl(ws_url_str).await { // Pass the original string URL eprintln!("REPL error: {}", e); } } Err(e) => { eprintln!("Invalid WebSocket URL format '{}': {}", ws_url_str, e); } } }