diff --git a/Cargo.toml b/Cargo.toml index a714f8c..0d6971f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,4 @@ tokio-util = "0.7" serde = "1.0" cli-log = "2.0" indexmap = "2.0.0" -mlua = { version = "0.8.9", features = ["lua54", "async"] } +mlua = { version = "0.8.9", features = ["lua54", "async", "send"] } diff --git a/src/app/command_interface.rs b/src/app/command_interface.rs index 1f4b9bc..6f74395 100644 --- a/src/app/command_interface.rs +++ b/src/app/command_interface.rs @@ -1,9 +1,11 @@ // FIXME: This file needs documentation with examples of how the proc macros work. // for now use `cargo expand app::command_interface` for an overview -use lua_macros::{ci_command, turn_struct_to_ci_commands}; -use super::events::event_types::Event; +use std::{io::{Error, ErrorKind}, sync::Arc}; +use lua_macros::{ci_command, turn_struct_to_ci_command_enum}; + +use crate::app::event_types::Event; /// This struct is here to guarantee, that all functions actually end up in the lua context. /// I.e. Rust should throw a compile error, when one field is added, but not a matching function. /// @@ -20,41 +22,28 @@ use super::events::event_types::Event; /// ``` /// where $return_type is the type returned by the function (the only supported ones are right now /// `String` and `()`). -#[turn_struct_to_ci_commands] + +#[turn_struct_to_ci_command_enum] struct Commands { /// Greets the user - greet: fn(usize) -> String, + greet: fn(String) -> String, /// Closes the application - #[gen_default_lua_function] + //#[expose(lua)] exit: fn(), /// Shows the command line - #[gen_default_lua_function] command_line_show: fn(), /// Hides the command line - #[gen_default_lua_function] command_line_hide: fn(), /// Go to the next plane - #[gen_default_lua_function] cycle_planes: fn(), /// Go to the previous plane - #[gen_default_lua_function] cycle_planes_rev: fn(), /// Send a message to the current room /// The send message is interpreted literally. room_message_send: fn(String) -> String, } - -#[ci_command] -async fn greet(lua: &mlua::Lua, input_str: String) -> Result { - Ok(format!("Name is {}", input_str)) -} - -#[ci_command] -async fn room_message_send(lua: &mlua::Lua, input_str: String) -> Result { - Ok(format!("Sent message: {}", input_str)) -} diff --git a/src/app/events/event_types/event/handlers/command.rs b/src/app/events/event_types/event/handlers/command.rs index 78a58ac..f058bd6 100644 --- a/src/app/events/event_types/event/handlers/command.rs +++ b/src/app/events/event_types/event/handlers/command.rs @@ -2,39 +2,65 @@ use crate::app::{command_interface::Command, events::event_types::EventStatus, A use anyhow::Result; use cli_log::info; -pub async fn handle(app: &mut App<'_>, command: &Command) -> Result { +pub async fn handle( + app: &mut App<'_>, + command: &Command, + send_output: bool, +) -> Result<(EventStatus, String)> { + macro_rules! set_status_output { + ($str:expr) => { + if send_output { + app.ui.set_command_output($str); + } + }; + ($str:expr, $($args:ident),+) => { + if send_output { + app.ui.set_command_output(&format!($str, $($args),+)); + } + }; + } info!("Handling command: {:#?}", command); Ok(match command { - Command::Exit => EventStatus::Terminate, + Command::Exit => ( + EventStatus::Terminate, + "Terminated the application".to_owned(), + ), Command::CommandLineShow => { app.ui.cli_enable(); - EventStatus::Ok + set_status_output!("CLI online"); + (EventStatus::Ok, "".to_owned()) } Command::CommandLineHide => { app.ui.cli_disable(); - EventStatus::Ok + set_status_output!("CLI offline"); + (EventStatus::Ok, "".to_owned()) } Command::CyclePlanes => { app.ui.cycle_main_input_position(); - EventStatus::Ok + set_status_output!("Switched main input position"); + (EventStatus::Ok, "".to_owned()) } Command::CyclePlanesRev => { app.ui.cycle_main_input_position_rev(); - EventStatus::Ok + set_status_output!("Switched main input position; reversed"); + (EventStatus::Ok, "".to_owned()) } Command::RoomMessageSend(msg) => { if let Some(room) = app.status.room_mut() { room.send(msg.clone()).await?; } - EventStatus::Ok + set_status_output!("Send message: `{}`", msg); + (EventStatus::Ok, "".to_owned()) } Command::Greet(name) => { info!("Greated {}", name); - EventStatus::Ok + set_status_output!("Hi, {}!", name); + (EventStatus::Ok, "".to_owned()) } + Command::Help(_) => todo!(), }) } diff --git a/src/app/events/event_types/event/handlers/lua_command.rs b/src/app/events/event_types/event/handlers/lua_command.rs index e6b60ea..e22833d 100644 --- a/src/app/events/event_types/event/handlers/lua_command.rs +++ b/src/app/events/event_types/event/handlers/lua_command.rs @@ -1,24 +1,38 @@ +use std::{sync::Arc, time::Duration}; + use anyhow::{Context, Result}; -use cli_log::info; +use cli_log::{debug, info}; +use tokio::{task, time::timeout}; -use crate::app::{ - events::event_types::{Event, EventStatus}, - App, -}; +use crate::app::{events::event_types::EventStatus, App}; -pub async fn handle(app: &mut App<'_>, command: &str) -> Result { +pub async fn handle(app: &mut App<'_>, command: String) -> Result { info!("Recieved ci command: `{command}`; executing.."); - // TODO: Should the ci support more than strings? - let output = app - .lua - .load(command) - .eval_async::() - .await - .with_context(|| format!("Failed to execute: `{command}`"))?; - info!("Function evaluated to: `{output}`"); + let local = task::LocalSet::new(); - app.tx.send(Event::CiOutput(output)).await.context("Failed to send ci output to internal event stream")?; + // Run the local task set. + let output = local + .run_until(async move { + let lua = Arc::clone(&app.lua); + debug!("before_handle"); + let c_handle = task::spawn_local(async move { + lua.load(&command) + // FIXME this assumes string output only + .eval_async::() + .await + .with_context(|| format!("Failed to execute: `{command}`")) + }); + debug!("after_handle"); + c_handle + }) + .await; + debug!("after_thread"); + + let output = timeout(Duration::from_secs(10), output) + .await + .context("Failed to join lua command executor")???; + info!("Command returned: `{}`", output); Ok(EventStatus::Ok) } diff --git a/src/app/events/event_types/event/handlers/main.rs b/src/app/events/event_types/event/handlers/main.rs index 4e87c40..5e076bc 100644 --- a/src/app/events/event_types/event/handlers/main.rs +++ b/src/app/events/event_types/event/handlers/main.rs @@ -16,14 +16,14 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { app.tx - .send(Event::CommandEvent(Command::Exit)) + .send(Event::CommandEvent(Command::Exit, None)) .await?; } CrosstermEvent::Key(KeyEvent { code: KeyCode::Tab, .. }) => { app.tx - .send(Event::CommandEvent(Command::CyclePlanes)) + .send(Event::CommandEvent(Command::CyclePlanes, None)) .await?; } CrosstermEvent::Key(KeyEvent { @@ -31,7 +31,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { app.tx - .send(Event::CommandEvent(Command::CyclePlanesRev)) + .send(Event::CommandEvent(Command::CyclePlanesRev, None)) .await?; } CrosstermEvent::Key(KeyEvent { @@ -40,7 +40,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { app.tx - .send(Event::CommandEvent(Command::CommandLineShow)) + .send(Event::CommandEvent(Command::CommandLineShow, None)) .await?; } input => match app.ui.input_position() { @@ -52,9 +52,10 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { app.tx - .send(Event::CommandEvent(Command::RoomMessageSend( - app.ui.message_compose.lines().join("\n"), - ))) + .send(Event::CommandEvent( + Command::RoomMessageSend(app.ui.message_compose.lines().join("\n")), + None, + )) .await?; app.ui.message_compose_clear(); } diff --git a/src/app/events/event_types/event/mod.rs b/src/app/events/event_types/event/mod.rs index 1e48bc6..65cc9ec 100644 --- a/src/app/events/event_types/event/mod.rs +++ b/src/app/events/event_types/event/mod.rs @@ -1,11 +1,13 @@ mod handlers; use anyhow::{Context, Result}; +use cli_log::{info, trace}; use crossterm::event::Event as CrosstermEvent; +use tokio::sync::mpsc::Sender; use crate::app::{command_interface::Command, status::State, App}; -use self::handlers::{ci_output, command, lua_command, main, matrix, setup}; +use self::handlers::{command, lua_command, main, matrix, setup}; use super::EventStatus; @@ -13,32 +15,39 @@ use super::EventStatus; pub enum Event { InputEvent(CrosstermEvent), MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse), - CommandEvent(Command), - CiOutput(String), + CommandEvent(Command, Option>), LuaCommand(String), } impl Event { pub async fn handle(&self, app: &mut App<'_>) -> Result { + trace!("Recieved event to handle: `{:#?}`", &self); match &self { Event::MatrixEvent(event) => matrix::handle(app, event) .await .with_context(|| format!("Failed to handle matrix event: `{:#?}`", event)), - Event::CommandEvent(event) => command::handle(app, event) - .await - .with_context(|| format!("Failed to handle command event: `{:#?}`", event)), - Event::CiOutput(output) => ci_output::handle(app, output).await.with_context(|| { - format!("Failed to handle command interface output: `{:#?}`", output) - }), - Event::LuaCommand(lua_code) => { - lua_command::handle(app, lua_code).await.with_context(|| { - format!("Failed to handle lua code: `{:#?}`", lua_code) - }) + Event::CommandEvent(event, callback_tx) => { + let (result, output) = command::handle(app, event, callback_tx.is_some()) + .await + .with_context(|| format!("Failed to handle command event: `{:#?}`", event))?; + + if let Some(callback_tx) = callback_tx { + callback_tx + .send(output.clone()) + .await + .with_context(|| format!("Failed to send command output: {}", output))?; + } + Ok(result) } + Event::LuaCommand(lua_code) => lua_command::handle(app, lua_code.to_owned()) + .await + .with_context(|| format!("Failed to handle lua code: `{:#?}`", lua_code)), Event::InputEvent(event) => match app.status.state() { - State::None => Ok(EventStatus::Ok), + State::None => unreachable!( + "This state should not be available, when we are in the input handling" + ), State::Main => main::handle(app, event) .await .with_context(|| format!("Failed to handle input event: `{:#?}`", event)), diff --git a/src/app/mod.rs b/src/app/mod.rs index e745aa3..e43f46a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2,9 +2,9 @@ pub mod command_interface; pub mod events; pub mod status; -use std::path::Path; +use std::{path::Path, sync::Arc}; -use anyhow::{Error, Result, Context}; +use anyhow::{Context, Error, Result}; use cli_log::info; use matrix_sdk::Client; use mlua::Lua; @@ -31,16 +31,16 @@ pub struct App<'ui> { input_listener_killer: CancellationToken, matrix_listener_killer: CancellationToken, - lua: Lua, + lua: Arc, } impl App<'_> { pub fn new() -> Result { - fn set_up_lua(tx: mpsc::Sender) -> Lua { + fn set_up_lua(tx: mpsc::Sender) -> Arc { let mut lua = Lua::new(); generate_ci_functions(&mut lua, tx); - lua + Arc::new(lua) } let path: &std::path::Path = Path::new("userdata/accounts.json");