Compare commits

...

6 Commits

16 changed files with 89 additions and 111 deletions

View File

@ -88,7 +88,7 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn {
.expect("This is valid."); .expect("This is valid.");
return_type_as_return_type return_type_as_return_type
} else { } else {
// There is only rlua // There is only mlua::Error left
ReturnType::Default ReturnType::Default
} }
} }
@ -108,23 +108,22 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn {
ReturnType::Type(_, _) => { ReturnType::Type(_, _) => {
quote! { quote! {
{ {
Event::CommandEvent(Command::#function_name_pascal(input_str)) Event::CommandEvent(Command::#function_name_pascal(input_str.clone()))
} }
} }
} }
}; };
quote! { quote! {
{ {
let tx: std::sync::Arc<std::sync::Mutex<tokio::sync::mpsc::Sender<crate::app::events::event_types::Event>>> = let tx:
core::cell::Ref<
tokio::sync::mpsc::Sender<crate::app::events::event_types::Event>
> =
lua lua
.named_registry_value("sender_for_ci_commands") .app_data_ref()
.expect("This exists, it was set before"); .expect("This exists, it was set before");
tx (*tx)
// FIXME: This is sync code, it needs to be run in a blocking allowed
// executor
.lock()
.expect("This should work, as only one function is executed. It wil however fail, when concurrency is added");
.send(#send_data) .send(#send_data)
.await .await
.expect("This should work, as the reciever is not dropped"); .expect("This should work, as the reciever is not dropped");

View File

@ -44,7 +44,7 @@ pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 {
lua: &mut mlua::Lua, lua: &mut mlua::Lua,
tx: tokio::sync::mpsc::Sender<crate::app::events::event_types::Event>) tx: tokio::sync::mpsc::Sender<crate::app::events::event_types::Event>)
{ {
lua.set_named_registry_value("sender_for_ci_commands", std::sync::Arc::new(std::sync::Mutex::new(tx))).expect("This should always work, as the value is added before all else"); lua.set_app_data(tx);
let globals = lua.globals(); let globals = lua.globals();
#input_tokens #input_tokens
} }

View File

@ -1,7 +1,6 @@
// FIXME: This file needs documentation with examples of how the proc macros work. // FIXME: This file needs documentation with examples of how the proc macros work.
// for now use `cargo expand app::command_interface` for an overview // for now use `cargo expand app::command_interface` for an overview
use lua_macros::{ci_command, turn_struct_to_ci_commands}; use lua_macros::{ci_command, turn_struct_to_ci_commands};
use rlua::Context;
use super::events::event_types::Event; use super::events::event_types::Event;
@ -23,22 +22,30 @@ use super::events::event_types::Event;
/// `String` and `()`). /// `String` and `()`).
#[turn_struct_to_ci_commands] #[turn_struct_to_ci_commands]
struct Commands { struct Commands {
/// Greets the user
greet: fn(usize) -> String, greet: fn(usize) -> String,
// Closes the application /// Closes the application
#[gen_default_lua_function] #[gen_default_lua_function]
exit: fn(), exit: fn(),
/// Shows the command line
#[gen_default_lua_function] #[gen_default_lua_function]
command_line_show: fn(), command_line_show: fn(),
/// Hides the command line
#[gen_default_lua_function] #[gen_default_lua_function]
command_line_hide: fn(), command_line_hide: fn(),
/// Go to the next plane
#[gen_default_lua_function] #[gen_default_lua_function]
cycle_planes: fn(), cycle_planes: fn(),
/// Go to the previous plane
#[gen_default_lua_function] #[gen_default_lua_function]
cycle_planes_rev: fn(), cycle_planes_rev: fn(),
//// sends a message to the current room /// Send a message to the current room
/// The send message is interpreted literally.
room_message_send: fn(String) -> String, room_message_send: fn(String) -> String,
} }

View File

@ -1,4 +1,4 @@
use crate::app::{events::event_types::EventStatus, App, command_interface::Command}; use crate::app::{command_interface::Command, events::event_types::EventStatus, App};
use anyhow::Result; use anyhow::Result;
use cli_log::info; use cli_log::info;
@ -32,5 +32,9 @@ pub async fn handle(app: &mut App<'_>, command: &Command) -> Result<EventStatus>
} }
EventStatus::Ok EventStatus::Ok
} }
Command::Greet(name) => {
info!("Greated {}", name);
EventStatus::Ok
}
}) })
} }

View File

@ -18,7 +18,7 @@ pub async fn handle(app: &mut App<'_>, command: &str) -> Result<EventStatus> {
.with_context(|| format!("Failed to execute: `{command}`"))?; .with_context(|| format!("Failed to execute: `{command}`"))?;
info!("Function evaluated to: `{output}`"); info!("Function evaluated to: `{output}`");
app.transmitter.send(Event::CiOutput(output)); app.tx.send(Event::CiOutput(output)).await.context("Failed to send ci output to internal event stream")?;
Ok(EventStatus::Ok) Ok(EventStatus::Ok)
} }

View File

@ -1,4 +1,4 @@
use anyhow::Result; use anyhow::{Context, Result};
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers}; use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers};
use crate::{ use crate::{
@ -15,14 +15,14 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
CrosstermEvent::Key(KeyEvent { CrosstermEvent::Key(KeyEvent {
code: KeyCode::Esc, .. code: KeyCode::Esc, ..
}) => { }) => {
app.transmitter app.tx
.send(Event::CommandEvent(Command::Exit)) .send(Event::CommandEvent(Command::Exit))
.await?; .await?;
} }
CrosstermEvent::Key(KeyEvent { CrosstermEvent::Key(KeyEvent {
code: KeyCode::Tab, .. code: KeyCode::Tab, ..
}) => { }) => {
app.transmitter app.tx
.send(Event::CommandEvent(Command::CyclePlanes)) .send(Event::CommandEvent(Command::CyclePlanes))
.await?; .await?;
} }
@ -30,7 +30,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
code: KeyCode::BackTab, code: KeyCode::BackTab,
.. ..
}) => { }) => {
app.transmitter app.tx
.send(Event::CommandEvent(Command::CyclePlanesRev)) .send(Event::CommandEvent(Command::CyclePlanesRev))
.await?; .await?;
} }
@ -39,7 +39,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
modifiers: KeyModifiers::CONTROL, modifiers: KeyModifiers::CONTROL,
.. ..
}) => { }) => {
app.transmitter app.tx
.send(Event::CommandEvent(Command::CommandLineShow)) .send(Event::CommandEvent(Command::CommandLineShow))
.await?; .await?;
} }
@ -51,7 +51,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
modifiers: KeyModifiers::ALT, modifiers: KeyModifiers::ALT,
.. ..
}) => { }) => {
app.transmitter app.tx
.send(Event::CommandEvent(Command::RoomMessageSend( .send(Event::CommandEvent(Command::RoomMessageSend(
app.ui.message_compose.lines().join("\n"), app.ui.message_compose.lines().join("\n"),
))) )))
@ -155,7 +155,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
code: KeyCode::Enter, code: KeyCode::Enter,
.. ..
}) => { }) => {
let cli_event = app.ui let ci_event = app.ui
.cli .cli
.as_mut() .as_mut()
.expect("This is already checked") .expect("This is already checked")
@ -165,16 +165,10 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
"There can only be one line in the buffer, as we collect it on enter being inputted" "There can only be one line in the buffer, as we collect it on enter being inputted"
) )
.to_owned(); .to_owned();
let output = app.handle_ci_event(&cli_event).await?; app.tx
.send(Event::LuaCommand(ci_event))
// delete the old text: .await
.context("Failed to send lua command to internal event stream")?;
// We can use a mutable borrow now, as we should only need one
let cli = app.ui.cli.as_mut().expect("Checked above");
cli.move_cursor(tui_textarea::CursorMove::Jump(0, 0));
cli.delete_str(0, cli_event.chars().count());
assert!(cli.is_empty());
cli.insert_str(output);
} }
_ => { _ => {
app.ui app.ui

View File

@ -1,4 +1,11 @@
pub mod command; // input events
pub mod main;
pub mod matrix;
pub mod setup; pub mod setup;
pub mod main;
// matrix
pub mod matrix;
// ci
pub mod ci_output;
pub mod command;
pub mod lua_command;

View File

@ -3,9 +3,9 @@ mod handlers;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use crossterm::event::Event as CrosstermEvent; use crossterm::event::Event as CrosstermEvent;
use crate::app::{status::State, App, command_interface::Command}; use crate::app::{command_interface::Command, status::State, App};
use self::handlers::{command, main, matrix, setup}; use self::handlers::{ci_output, command, lua_command, main, matrix, setup};
use super::EventStatus; use super::EventStatus;
@ -14,6 +14,8 @@ pub enum Event {
InputEvent(CrosstermEvent), InputEvent(CrosstermEvent),
MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse), MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse),
CommandEvent(Command), CommandEvent(Command),
CiOutput(String),
LuaCommand(String),
} }
impl Event { impl Event {
@ -26,6 +28,14 @@ impl Event {
Event::CommandEvent(event) => command::handle(app, event) Event::CommandEvent(event) => command::handle(app, event)
.await .await
.with_context(|| format!("Failed to handle command event: `{:#?}`", event)), .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::InputEvent(event) => match app.status.state() { Event::InputEvent(event) => match app.status.state() {
State::None => Ok(EventStatus::Ok), State::None => Ok(EventStatus::Ok),

View File

@ -1,11 +1,10 @@
pub mod command_interface; pub mod command_interface;
pub mod events; pub mod events;
pub mod status; pub mod status;
pub mod transmitter;
use std::path::Path; use std::path::Path;
use anyhow::{Context, Error, Result}; use anyhow::{Error, Result, Context};
use cli_log::info; use cli_log::info;
use matrix_sdk::Client; use matrix_sdk::Client;
use mlua::Lua; use mlua::Lua;
@ -19,14 +18,16 @@ use crate::{
ui::{central, setup}, ui::{central, setup},
}; };
use self::{events::event_types, transmitter::Transmitter}; use self::events::event_types;
pub struct App<'ui> { pub struct App<'ui> {
ui: central::UI<'ui>, ui: central::UI<'ui>,
accounts_manager: AccountsManager, accounts_manager: AccountsManager,
status: Status, status: Status,
transmitter: Transmitter, tx: mpsc::Sender<Event>,
rx: mpsc::Receiver<Event>,
input_listener_killer: CancellationToken, input_listener_killer: CancellationToken,
matrix_listener_killer: CancellationToken, matrix_listener_killer: CancellationToken,
@ -36,7 +37,7 @@ pub struct App<'ui> {
impl App<'_> { impl App<'_> {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
fn set_up_lua(tx: mpsc::Sender<Event>) -> Lua { fn set_up_lua(tx: mpsc::Sender<Event>) -> Lua {
let lua = Lua::new(); let mut lua = Lua::new();
generate_ci_functions(&mut lua, tx); generate_ci_functions(&mut lua, tx);
lua lua
@ -50,39 +51,25 @@ impl App<'_> {
None None
}; };
let transmitter = Transmitter::new(); let (tx, rx) = mpsc::channel(256);
Ok(Self { Ok(Self {
ui: central::UI::new()?, ui: central::UI::new()?,
accounts_manager: AccountsManager::new(config)?, accounts_manager: AccountsManager::new(config)?,
status: Status::new(None), status: Status::new(None),
transmitter, tx: tx.clone(),
rx,
input_listener_killer: CancellationToken::new(), input_listener_killer: CancellationToken::new(),
matrix_listener_killer: CancellationToken::new(), matrix_listener_killer: CancellationToken::new(),
lua: set_up_lua(transmitter.tx()), lua: set_up_lua(tx),
}) })
} }
pub async fn handle_ci_event(&self, event: &str) -> Result<String> {
info!("Recieved ci event: `{event}`; executing..");
// TODO: Should the ci support more than strings?
let output = self.lua.context(|context| -> Result<String> {
let output = context
.load(&event)
.eval::<String>()
.with_context(|| format!("Failed to execute: `{event}`"))?;
info!("Function evaluated to: `{output}`");
Ok(output)
})?;
Ok(output)
}
pub async fn run(&mut self) -> Result<()> { pub async fn run(&mut self) -> Result<()> {
// Spawn input event listener // Spawn input event listener
tokio::task::spawn(events::poll_input_events( tokio::task::spawn(events::poll_input_events(
self.transmitter.tx(), self.tx.clone(),
self.input_listener_killer.clone(), self.input_listener_killer.clone(),
)); ));
@ -98,11 +85,7 @@ impl App<'_> {
self.status.set_state(State::Main); self.status.set_state(State::Main);
self.ui.update(&self.status).await?; self.ui.update(&self.status).await?;
let event = self let event = self.rx.recv().await.context("Failed to get next event")?;
.transmitter
.recv()
.await
.context("Failed to get next event")?;
match event.handle(self).await? { match event.handle(self).await? {
event_types::EventStatus::Ok => (), event_types::EventStatus::Ok => (),
@ -122,11 +105,7 @@ impl App<'_> {
self.status.set_state(State::Setup); self.status.set_state(State::Setup);
self.ui.update_setup().await?; self.ui.update_setup().await?;
let event = self let event = self.rx.recv().await.context("Failed to get next event")?;
.transmitter
.recv()
.await
.context("Failed to get next event")?;
match event.handle(self).await? { match event.handle(self).await? {
event_types::EventStatus::Ok => (), event_types::EventStatus::Ok => (),
@ -150,7 +129,7 @@ impl App<'_> {
// Spawn Matrix Event Listener // Spawn Matrix Event Listener
tokio::task::spawn(events::poll_matrix_events( tokio::task::spawn(events::poll_matrix_events(
self.transmitter.tx(), self.tx.clone(),
self.matrix_listener_killer.clone(), self.matrix_listener_killer.clone(),
client.clone(), client.clone(),
)); ));

View File

@ -1,5 +1,5 @@
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use cli_log::{warn, info}; use cli_log::warn;
use indexmap::IndexMap; use indexmap::IndexMap;
use matrix_sdk::{ use matrix_sdk::{
room::MessagesOptions, room::MessagesOptions,

View File

@ -1,32 +0,0 @@
use anyhow::{bail, Context, Result};
use tokio::sync::mpsc;
use super::events::event_types::Event;
pub struct Transmitter {
tx: mpsc::Sender<Event>,
rx: mpsc::Receiver<Event>,
}
impl Transmitter {
pub fn new() -> Transmitter {
let (tx, rx) = mpsc::channel(256);
Transmitter {
tx,
rx,
}
}
pub fn tx(&self) -> mpsc::Sender<Event> {
self.tx.to_owned()
}
pub async fn recv(&mut self) -> Result<Event> {
match self.rx.recv().await {
Some(event) => Ok(event),
None => bail!("Event channel has no senders"),
}
}
pub async fn send(&mut self, event: Event) -> Result<()> {
self.tx.send(event).await.context("Failed to send event")
}
}

View File

@ -2,8 +2,8 @@ pub mod update;
use std::io::Stdout; use std::io::Stdout;
use anyhow::{bail, Result, Context}; use anyhow::{bail, Context, Result};
use cli_log::info; use cli_log::{info, warn};
use crossterm::{ use crossterm::{
event::DisableMouseCapture, event::DisableMouseCapture,
execute, execute,
@ -125,6 +125,16 @@ impl UI<'_> {
); );
} }
pub fn set_command_output(&mut self, output: &str) {
info!("Setting output to: `{}`", output);
if let Some(_) = self.cli {
let cli = Some(TextArea::from([output]));
self.cli = cli;
} else {
warn!("Failed to set output");
}
}
pub fn cli_enable(&mut self) { pub fn cli_enable(&mut self) {
self.input_position = InputPosition::CLI; self.input_position = InputPosition::CLI;
if self.cli.is_some() { if self.cli.is_some() {

View File

@ -9,7 +9,7 @@ use tui::{
use crate::{app::status::Room, ui::central::InputPosition}; use crate::{app::status::Room, ui::central::InputPosition};
pub fn init<'a>(room: Option<&Room>, colors: &Vec<Color>) -> Result<(List<'a>, ListState)> { pub fn init<'a>(room: Option<&'a Room>, colors: &Vec<Color>) -> Result<(List<'a>, ListState)> {
let content = match room { let content = match room {
Some(room) => get_content_from_room(room).context("Failed to get content from room")?, Some(room) => get_content_from_room(room).context("Failed to get content from room")?,
None => vec![ListItem::new(Text::styled( None => vec![ListItem::new(Text::styled(

View File

@ -6,7 +6,7 @@ use tui::{
use crate::{app::status::Room, ui::central::InputPosition}; use crate::{app::status::Room, ui::central::InputPosition};
pub fn init<'a>(room: Option<&Room>, colors: &Vec<Color>) -> Paragraph<'a> { pub fn init<'a>(room: Option<&'a Room>, colors: &Vec<Color>) -> Paragraph<'a> {
let mut room_info_content = Text::default(); let mut room_info_content = Text::default();
if let Some(room) = room { if let Some(room) = room {
room_info_content.extend(Text::styled(room.name(), Style::default().fg(Color::Cyan))); room_info_content.extend(Text::styled(room.name(), Style::default().fg(Color::Cyan)));

View File

@ -6,7 +6,7 @@ use tui::{
use crate::{app::status::Status, ui::central::InputPosition}; use crate::{app::status::Status, ui::central::InputPosition};
pub fn init<'a>(status: &Status, colors: &Vec<Color>) -> List<'a> { pub fn init<'a>(status: &'a Status, colors: &Vec<Color>) -> List<'a> {
let rooms_content: Vec<_> = status let rooms_content: Vec<_> = status
.rooms() .rooms()
.iter() .iter()

View File

@ -7,7 +7,7 @@ use tui::{
use crate::{app::status::Status, ui::central::InputPosition}; use crate::{app::status::Status, ui::central::InputPosition};
pub fn init<'a>(status: &Status, colors: &Vec<Color>) -> Paragraph<'a> { pub fn init<'a>(status: &'a Status, colors: &Vec<Color>) -> Paragraph<'a> {
let mut status_content = Text::styled( let mut status_content = Text::styled(
status.account_name(), status.account_name(),
Style::default().add_modifier(Modifier::BOLD), Style::default().add_modifier(Modifier::BOLD),