From 20c155d58a2508df6fa1de908c3614adc2f119cf Mon Sep 17 00:00:00 2001 From: Soispha Date: Sun, 23 Jul 2023 16:36:58 +0200 Subject: [PATCH] sqoush --- lua_macros/src/new/lib.rs | 26 +++ .../events/event/handlers/command.rs | 66 ++++++ .../events/event/handlers/lua_command.rs | 21 ++ .../events/event/handlers/main.rs | 188 ++++++++++++++++++ .../events/event/handlers/matrix.rs | 23 +++ .../events/event/handlers/mod.rs | 10 + .../events/event/handlers/setup.rs | 72 +++++++ src/event_handler/events/event/mod.rs | 60 ++++++ src/event_handler/events/event_status.rs | 6 + src/event_handler/events/mod.rs | 5 + src/event_handler/mod.rs | 85 ++++++++ src/event_handler/poll_functions.rs | 60 ++++++ 12 files changed, 622 insertions(+) create mode 100644 lua_macros/src/new/lib.rs create mode 100644 src/event_handler/events/event/handlers/command.rs create mode 100644 src/event_handler/events/event/handlers/lua_command.rs create mode 100644 src/event_handler/events/event/handlers/main.rs create mode 100644 src/event_handler/events/event/handlers/matrix.rs create mode 100644 src/event_handler/events/event/handlers/mod.rs create mode 100644 src/event_handler/events/event/handlers/setup.rs create mode 100644 src/event_handler/events/event/mod.rs create mode 100644 src/event_handler/events/event_status.rs create mode 100644 src/event_handler/events/mod.rs create mode 100644 src/event_handler/mod.rs create mode 100644 src/event_handler/poll_functions.rs diff --git a/lua_macros/src/new/lib.rs b/lua_macros/src/new/lib.rs new file mode 100644 index 0000000..7e7a507 --- /dev/null +++ b/lua_macros/src/new/lib.rs @@ -0,0 +1,26 @@ +#[proc_macro_attribute] +pub fn turn_struct_to_ci_command_enum(_attrs: TokenStream, input: TokenStream) -> TokenStream { + // Construct a representation of Rust code as a syntax tree + // that we can manipulate + let mut input: DeriveInput = syn::parse(input).expect("This should always be valid rust code, as it's extracted from direct code"); + + let mut named_fields = match &input.data { + syn::Data::Struct(input) => match &input.fields { + syn::Fields::Named(named_fields) => named_fields, + _ => unimplemented!("The macro only works for named fields (e.g.: `Name: Type`)"), + }, + _ => unimplemented!("The macro only works for structs"), + } + .to_owned(); + + + // Build the trait implementation + let build_lua_functions: TokenStream2 = genrate::build_lua_functions(&input); + + let command_enum = generate::command_enum(&named_fields); + + quote! { + } + .into() +} + diff --git a/src/event_handler/events/event/handlers/command.rs b/src/event_handler/events/event/handlers/command.rs new file mode 100644 index 0000000..f058bd6 --- /dev/null +++ b/src/event_handler/events/event/handlers/command.rs @@ -0,0 +1,66 @@ +use crate::app::{command_interface::Command, events::event_types::EventStatus, App}; +use anyhow::Result; +use cli_log::info; + +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, + "Terminated the application".to_owned(), + ), + + Command::CommandLineShow => { + app.ui.cli_enable(); + set_status_output!("CLI online"); + (EventStatus::Ok, "".to_owned()) + } + Command::CommandLineHide => { + app.ui.cli_disable(); + set_status_output!("CLI offline"); + (EventStatus::Ok, "".to_owned()) + } + + Command::CyclePlanes => { + app.ui.cycle_main_input_position(); + set_status_output!("Switched main input position"); + (EventStatus::Ok, "".to_owned()) + } + Command::CyclePlanesRev => { + app.ui.cycle_main_input_position_rev(); + 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?; + } + set_status_output!("Send message: `{}`", msg); + (EventStatus::Ok, "".to_owned()) + } + Command::Greet(name) => { + info!("Greated {}", name); + set_status_output!("Hi, {}!", name); + (EventStatus::Ok, "".to_owned()) + } + Command::Help(_) => todo!(), + }) +} diff --git a/src/event_handler/events/event/handlers/lua_command.rs b/src/event_handler/events/event/handlers/lua_command.rs new file mode 100644 index 0000000..0a864c4 --- /dev/null +++ b/src/event_handler/events/event/handlers/lua_command.rs @@ -0,0 +1,21 @@ +use std::time::Duration; + +use anyhow::{Context, Result}; +use cli_log::info; +use tokio::time::timeout; + +use crate::app::{events::event_types::EventStatus, App}; + +pub async fn handle(app: &mut App<'_>, command: String) -> Result { + info!("Recieved ci command: `{command}`; executing.."); + + app.lua_command_tx + .send(command.clone()) + .await + .with_context(|| format!("Failed to execute: `{}`", command))?; + + + //let output = timeout(Duration::from_secs(5), run_command(app, command)).await??; + + Ok(EventStatus::Ok) +} diff --git a/src/event_handler/events/event/handlers/main.rs b/src/event_handler/events/event/handlers/main.rs new file mode 100644 index 0000000..5e076bc --- /dev/null +++ b/src/event_handler/events/event/handlers/main.rs @@ -0,0 +1,188 @@ +use anyhow::{Context, Result}; +use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers}; + +use crate::{ + app::{ + command_interface::Command, + events::event_types::{Event, EventStatus}, + App, + }, + ui::central, +}; + +pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { + match input_event { + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Esc, .. + }) => { + app.tx + .send(Event::CommandEvent(Command::Exit, None)) + .await?; + } + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Tab, .. + }) => { + app.tx + .send(Event::CommandEvent(Command::CyclePlanes, None)) + .await?; + } + CrosstermEvent::Key(KeyEvent { + code: KeyCode::BackTab, + .. + }) => { + app.tx + .send(Event::CommandEvent(Command::CyclePlanesRev, None)) + .await?; + } + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + app.tx + .send(Event::CommandEvent(Command::CommandLineShow, None)) + .await?; + } + input => match app.ui.input_position() { + central::InputPosition::MessageCompose => { + match input { + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Enter, + modifiers: KeyModifiers::ALT, + .. + }) => { + app.tx + .send(Event::CommandEvent( + Command::RoomMessageSend(app.ui.message_compose.lines().join("\n")), + None, + )) + .await?; + app.ui.message_compose_clear(); + } + _ => { + app.ui + .message_compose + .input(tui_textarea::Input::from(input.to_owned())); + } + }; + } + central::InputPosition::Rooms => { + match input { + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Up, .. + }) => { + let i = match app.ui.rooms_state.selected() { + Some(cur) => { + if cur > 0 { + cur - 1 + } else { + cur + } + } + None => 0, + }; + app.ui.rooms_state.select(Some(i)); + app.status.set_room_by_index(i)?; + } + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Down, + .. + }) => { + let i = match app.ui.rooms_state.selected() { + Some(cur) => { + if cur < app.status.rooms().len() - 1 { + cur + 1 + } else { + cur + } + } + None => 0, + }; + app.ui.rooms_state.select(Some(i)); + app.status.set_room_by_index(i)?; + } + _ => (), + }; + } + central::InputPosition::Messages => { + match input { + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Up, .. + }) => { + match app.status.room_mut() { + Some(room) => { + let len = room.timeline().len(); + let i = match room.view_scroll() { + Some(i) => i + 1, + None => 0, + }; + if i < len { + room.set_view_scroll(Some(i)) + } + if i <= len - 5 { + room.poll_old_timeline().await?; + } + } + None => (), + }; + } + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Down, + .. + }) => { + match app.status.room_mut() { + Some(room) => { + match room.view_scroll() { + Some(i) => { + if i == 0 { + room.set_view_scroll(None); + } else { + room.set_view_scroll(Some(i - 1)); + } + } + None => (), + }; + } + None => (), + }; + } + _ => (), + }; + } + central::InputPosition::CLI => { + if let Some(_) = app.ui.cli { + match input { + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Enter, + .. + }) => { + let ci_event = app.ui + .cli + .as_mut() + .expect("This is already checked") + .lines() + .get(0) + .expect( + "There can only be one line in the buffer, as we collect it on enter being inputted" + ) + .to_owned(); + app.tx + .send(Event::LuaCommand(ci_event)) + .await + .context("Failed to send lua command to internal event stream")?; + } + _ => { + app.ui + .cli + .as_mut() + .expect("This is already checked") + .input(tui_textarea::Input::from(input.to_owned())); + } + }; + }; + } + _ => (), + }, + }; + Ok(EventStatus::Ok) +} diff --git a/src/event_handler/events/event/handlers/matrix.rs b/src/event_handler/events/event/handlers/matrix.rs new file mode 100644 index 0000000..5215a28 --- /dev/null +++ b/src/event_handler/events/event/handlers/matrix.rs @@ -0,0 +1,23 @@ +use matrix_sdk::deserialized_responses::SyncResponse; +use anyhow::Result; + +use crate::app::{events::event_types::EventStatus, App}; + +pub async fn handle<'a>(app: &mut App<'a>, sync: &SyncResponse) -> Result { + for (m_room_id, m_room) in sync.rooms.join.iter() { + let room = match app.status.get_room_mut(m_room_id) { + Some(r) => r, + None => continue, + }; + for m_event in m_room.timeline.events.clone() { + let event = m_event + .event + .deserialize() + .unwrap() + .into_full_event(m_room_id.clone()); + room.timeline_add(event); + } + } + + Ok(EventStatus::Ok) +} diff --git a/src/event_handler/events/event/handlers/mod.rs b/src/event_handler/events/event/handlers/mod.rs new file mode 100644 index 0000000..2c08e0d --- /dev/null +++ b/src/event_handler/events/event/handlers/mod.rs @@ -0,0 +1,10 @@ +// input events +pub mod main; +pub mod setup; + +// matrix +pub mod matrix; + +// ci +pub mod command; +pub mod lua_command; diff --git a/src/event_handler/events/event/handlers/setup.rs b/src/event_handler/events/event/handlers/setup.rs new file mode 100644 index 0000000..8e22128 --- /dev/null +++ b/src/event_handler/events/event/handlers/setup.rs @@ -0,0 +1,72 @@ +use anyhow::{bail, Context, Result}; +use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent}; + +use crate::{app::{events::event_types::EventStatus, App}, ui::setup}; + +pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { + let ui = match &mut app.ui.setup_ui { + Some(ui) => ui, + None => bail!("SetupUI instance not found"), + }; + + match input_event { + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Esc, .. + }) => return Ok(EventStatus::Terminate), + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Tab, .. + }) => { + ui.cycle_input_position(); + } + CrosstermEvent::Key(KeyEvent { + code: KeyCode::BackTab, + .. + }) => { + ui.cycle_input_position_rev(); + } + CrosstermEvent::Key(KeyEvent { + code: KeyCode::Enter, + .. + }) => { + match ui.input_position() { + setup::InputPosition::Ok => { + let homeserver = ui.homeserver.lines()[0].clone(); + let username = ui.username.lines()[0].clone(); + let password = ui.password_data.lines()[0].clone(); + app.login(&homeserver, &username, &password) + .await + .context("Failed to login")?; + // We bailed in the line above, thus login must have succeeded + return Ok(EventStatus::Finished); + } + _ => ui.cycle_input_position(), + }; + } + input => match ui.input_position() { + setup::InputPosition::Homeserver => { + ui.homeserver.input(input.to_owned()); + } + setup::InputPosition::Username => { + ui.username.input(input.to_owned()); + } + setup::InputPosition::Password => { + let textarea_input = tui_textarea::Input::from(input.to_owned()); + ui.password_data.input(textarea_input.clone()); + match textarea_input.key { + tui_textarea::Key::Char(_) => { + ui.password.input(tui_textarea::Input { + key: tui_textarea::Key::Char('*'), + ctrl: false, + alt: false, + }); + } + _ => { + ui.password.input(textarea_input); + } + } + } + _ => (), + }, + }; + Ok(EventStatus::Ok) +} diff --git a/src/event_handler/events/event/mod.rs b/src/event_handler/events/event/mod.rs new file mode 100644 index 0000000..65cc9ec --- /dev/null +++ b/src/event_handler/events/event/mod.rs @@ -0,0 +1,60 @@ +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::{command, lua_command, main, matrix, setup}; + +use super::EventStatus; + +#[derive(Debug)] +pub enum Event { + InputEvent(CrosstermEvent), + MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse), + 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, 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 => 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)), + State::Setup => setup::handle(app, event) + .await + .with_context(|| format!("Failed to handle input event: `{:#?}`", event)), + }, + } + } +} diff --git a/src/event_handler/events/event_status.rs b/src/event_handler/events/event_status.rs new file mode 100644 index 0000000..d7642cb --- /dev/null +++ b/src/event_handler/events/event_status.rs @@ -0,0 +1,6 @@ +#[derive(Debug)] +pub enum EventStatus { + Ok, + Finished, + Terminate, +} diff --git a/src/event_handler/events/mod.rs b/src/event_handler/events/mod.rs new file mode 100644 index 0000000..78356d6 --- /dev/null +++ b/src/event_handler/events/mod.rs @@ -0,0 +1,5 @@ +pub mod event; +pub mod event_status; + +pub use self::event::*; +pub use self::event_status::*; diff --git a/src/event_handler/mod.rs b/src/event_handler/mod.rs new file mode 100644 index 0000000..99af7ec --- /dev/null +++ b/src/event_handler/mod.rs @@ -0,0 +1,85 @@ +use tokio::{ + runtime::Builder, + sync::{mpsc, oneshot}, + task::LocalSet, +}; + +#[cfg(feature = "tui")] +mod poll_functions; + +mod events; + +// This struct describes the task you want to spawn. Here we include +// some simple examples. The oneshot channel allows sending a response +// to the spawner. +#[derive(Debug)] +enum Task { + PrintNumber(u32), + AddOne(u32, oneshot::Sender), +} + +#[derive(Clone)] +struct LocalSpawner { + send: mpsc::UnboundedSender, +} + +impl LocalSpawner { + pub fn new() -> Self { + let (send, mut recv) = mpsc::unbounded_channel(); + + let rt = Builder::new_current_thread().enable_all().build().unwrap(); + + std::thread::spawn(move || { + let local = LocalSet::new(); + + local.spawn_local(async move { + while let Some(new_task) = recv.recv().await { + tokio::task::spawn_local(run_task(new_task)); + } + // If the while loop returns, then all the LocalSpawner + // objects have been dropped. + }); + + // This will return once all senders are dropped and all + // spawned tasks have returned. + rt.block_on(local); + }); + + Self { send } + } + + pub fn spawn(&self, task: Task) { + self.send + .send(task) + .expect("Thread with LocalSet has shut down."); + } +} + +// This task may do !Send stuff. We use printing a number as an example, +// but it could be anything. +// +// The Task struct is an enum to support spawning many different kinds +// of operations. +async fn run_task(task: Task) { + match task { + Task::PrintNumber(n) => { + println!("{}", n); + } + Task::AddOne(n, response) => { + // We ignore failures to send the response. + let _ = response.send(n + 1); + } + } +} + +#[tokio::main] +async fn main() { + let spawner = LocalSpawner::new(); + + let (send, response) = oneshot::channel(); + spawner.spawn(Task::AddOne(10, send)); + let eleven = response.await.unwrap(); + + spawner.spawn(Task::PrintNumber(eleven)); + assert_eq!(eleven, 11); +} diff --git a/src/event_handler/poll_functions.rs b/src/event_handler/poll_functions.rs new file mode 100644 index 0000000..7fe5e37 --- /dev/null +++ b/src/event_handler/poll_functions.rs @@ -0,0 +1,60 @@ +use anyhow::{bail, Result}; +use matrix_sdk::{config::SyncSettings, Client, LoopCtrl}; +use tokio::{sync::mpsc, time::Duration}; +use tokio_util::sync::CancellationToken; + +use self::event_types::Event; + +pub async fn poll_input_events( + channel: mpsc::Sender, + kill: CancellationToken, +) -> Result<()> { + async fn poll_input_events_stage_2(channel: mpsc::Sender) -> Result<()> { + loop { + if crossterm::event::poll(Duration::from_millis(100))? { + let event = Event::InputEvent(crossterm::event::read()?); + channel.send(event).await?; + } else { + tokio::task::yield_now().await; + } + } + } + tokio::select! { + output = poll_input_events_stage_2(channel) => output, + _ = kill.cancelled() => bail!("received kill signal") + } +} + +pub async fn poll_matrix_events( + channel: mpsc::Sender, + kill: CancellationToken, + client: Client, +) -> Result<()> { + async fn poll_matrix_events_stage_2( + channel: mpsc::Sender, + client: Client, + ) -> Result<()> { + let sync_settings = SyncSettings::default(); + // .token(sync_token) + // .timeout(Duration::from_secs(30)); + + let tx = &channel; + + client + .sync_with_callback(sync_settings, |response| async move { + let event = Event::MatrixEvent(response); + + match tx.send(event).await { + Ok(_) => LoopCtrl::Continue, + Err(_) => LoopCtrl::Break, + } + }) + .await?; + + Ok(()) + } + tokio::select! { + output = poll_matrix_events_stage_2(channel, client) => output, + _ = kill.cancelled() => bail!("received kill signal"), + } +}