From 8f9a2a3f229fc9839f4fb1f269656c45764a4265 Mon Sep 17 00:00:00 2001 From: Soispha Date: Sat, 15 Jul 2023 12:29:35 +0200 Subject: [PATCH 01/18] Refactor(ui): Split into multiple files --- .../events/event_types/event/handlers/main.rs | 10 +- .../event_types/event/handlers/setup.rs | 13 +- src/app/mod.rs | 8 +- src/ui/central/mod.rs | 155 +++++ src/ui/central/update/mod.rs | 167 +++++ src/ui/central/update/widgets/messages.rs | 109 ++++ src/ui/central/update/widgets/mod.rs | 4 + src/ui/central/update/widgets/room_info.rs | 36 ++ src/ui/central/update/widgets/rooms.rs | 29 + src/ui/central/update/widgets/status.rs | 30 + src/ui/mod.rs | 609 +----------------- src/ui/setup.rs | 172 +++++ 12 files changed, 725 insertions(+), 617 deletions(-) create mode 100644 src/ui/central/mod.rs create mode 100644 src/ui/central/update/mod.rs create mode 100644 src/ui/central/update/widgets/messages.rs create mode 100644 src/ui/central/update/widgets/mod.rs create mode 100644 src/ui/central/update/widgets/room_info.rs create mode 100644 src/ui/central/update/widgets/rooms.rs create mode 100644 src/ui/central/update/widgets/status.rs create mode 100644 src/ui/setup.rs diff --git a/src/app/events/event_types/event/handlers/main.rs b/src/app/events/event_types/event/handlers/main.rs index 8e3bdd1..880339a 100644 --- a/src/app/events/event_types/event/handlers/main.rs +++ b/src/app/events/event_types/event/handlers/main.rs @@ -3,7 +3,7 @@ use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers} use crate::{ app::{command, command::Command, events::event_types::EventStatus, App}, - ui, + ui::central, }; pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { @@ -32,7 +32,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result match app.ui.input_position() { - ui::MainInputPosition::MessageCompose => { + central::InputPosition::MessageCompose => { match input { CrosstermEvent::Key(KeyEvent { code: KeyCode::Enter, @@ -53,7 +53,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { + central::InputPosition::Rooms => { match input { CrosstermEvent::Key(KeyEvent { code: KeyCode::Up, .. @@ -91,7 +91,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result (), }; } - ui::MainInputPosition::Messages => { + central::InputPosition::Messages => { match input { CrosstermEvent::Key(KeyEvent { code: KeyCode::Up, .. @@ -136,7 +136,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result (), }; } - ui::MainInputPosition::CLI => { + central::InputPosition::CLI => { if let Some(_) = app.ui.cli { match input { CrosstermEvent::Key(KeyEvent { diff --git a/src/app/events/event_types/event/handlers/setup.rs b/src/app/events/event_types/event/handlers/setup.rs index 38cafb2..8e22128 100644 --- a/src/app/events/event_types/event/handlers/setup.rs +++ b/src/app/events/event_types/event/handlers/setup.rs @@ -1,10 +1,7 @@ use anyhow::{bail, Context, Result}; use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent}; -use crate::{ - app::{events::event_types::EventStatus, App}, - ui, -}; +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 { @@ -32,7 +29,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { match ui.input_position() { - ui::SetupInputPosition::Ok => { + 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(); @@ -46,13 +43,13 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result match ui.input_position() { - ui::SetupInputPosition::Homeserver => { + setup::InputPosition::Homeserver => { ui.homeserver.input(input.to_owned()); } - ui::SetupInputPosition::Username => { + setup::InputPosition::Username => { ui.username.input(input.to_owned()); } - ui::SetupInputPosition::Password => { + setup::InputPosition::Password => { let textarea_input = tui_textarea::Input::from(input.to_owned()); ui.password_data.input(textarea_input.clone()); match textarea_input.key { diff --git a/src/app/mod.rs b/src/app/mod.rs index 10652c9..785fa8a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -14,12 +14,12 @@ use status::{State, Status}; use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; -use crate::{accounts, app::command_interface::generate_ci_functions, ui}; +use crate::{accounts, app::command_interface::generate_ci_functions, ui::{central, setup}}; use self::events::event_types::{self, Event}; pub struct App<'ui> { - ui: ui::UI<'ui>, + ui: central::UI<'ui>, accounts_manager: accounts::AccountsManager, status: Status, @@ -53,7 +53,7 @@ impl App<'_> { let (channel_tx, channel_rx) = mpsc::channel(256); Ok(Self { - ui: ui::UI::new()?, + ui: central::UI::new()?, accounts_manager: AccountsManager::new(config)?, status: Status::new(None), @@ -117,7 +117,7 @@ impl App<'_> { } async fn setup(&mut self) -> Result<()> { - self.ui.setup_ui = Some(ui::SetupUI::new()); + self.ui.setup_ui = Some(setup::UI::new()); loop { self.status.set_state(State::Setup); diff --git a/src/ui/central/mod.rs b/src/ui/central/mod.rs new file mode 100644 index 0000000..e03e658 --- /dev/null +++ b/src/ui/central/mod.rs @@ -0,0 +1,155 @@ +pub mod update; + +use std::io::Stdout; + +use anyhow::{bail, Result, Context}; +use cli_log::info; +use crossterm::{ + event::DisableMouseCapture, + execute, + terminal::{disable_raw_mode, LeaveAlternateScreen}, +}; +use tui::{ + backend::CrosstermBackend, + widgets::{Block, Borders, ListState}, + Terminal, +}; +use tui_textarea::TextArea; + +use crate::ui::terminal_prepare; + +use super::setup; + +pub use update::*; + +#[derive(Clone, Copy, PartialEq)] +pub enum InputPosition { + Status, + Rooms, + Messages, + MessageCompose, + RoomInfo, + CLI, +} + +pub struct UI<'a> { + terminal: Terminal>, + input_position: InputPosition, + pub rooms_state: ListState, + pub message_compose: TextArea<'a>, + pub cli: Option>, + + pub setup_ui: Option>, +} + +impl Drop for UI<'_> { + fn drop(&mut self) { + info!("Destructing UI"); + disable_raw_mode().expect("While destructing UI -> Failed to disable raw mode"); + execute!( + self.terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + ).expect("While destructing UI -> Failed execute backend commands (LeaveAlternateScreen and DisableMouseCapture)"); + self.terminal + .show_cursor() + .expect("While destructing UI -> Failed to re-enable cursor"); + } +} + +impl UI<'_> { + pub fn new() -> Result { + let stdout = terminal_prepare().context("Falied to prepare terminal")?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + terminal.clear()?; + + let mut message_compose = TextArea::default(); + message_compose.set_block( + Block::default() + .title("Message Compose (send: +)") + .borders(Borders::ALL), + ); + + info!("Initialized UI"); + + Ok(Self { + terminal, + input_position: InputPosition::Rooms, + rooms_state: ListState::default(), + message_compose, + cli: None, + setup_ui: None, + }) + } + + pub fn cycle_main_input_position(&mut self) { + self.input_position = match self.input_position { + InputPosition::Status => InputPosition::Rooms, + InputPosition::Rooms => InputPosition::Messages, + InputPosition::Messages => InputPosition::MessageCompose, + InputPosition::MessageCompose => InputPosition::RoomInfo, + InputPosition::RoomInfo => match self.cli { + Some(_) => InputPosition::CLI, + None => InputPosition::Status, + }, + InputPosition::CLI => InputPosition::Status, + }; + } + + pub fn cycle_main_input_position_rev(&mut self) { + self.input_position = match self.input_position { + InputPosition::Status => match self.cli { + Some(_) => InputPosition::CLI, + None => InputPosition::RoomInfo, + }, + InputPosition::Rooms => InputPosition::Status, + InputPosition::Messages => InputPosition::Rooms, + InputPosition::MessageCompose => InputPosition::Messages, + InputPosition::RoomInfo => InputPosition::MessageCompose, + InputPosition::CLI => InputPosition::RoomInfo, + }; + } + + pub fn input_position(&self) -> &InputPosition { + &self.input_position + } + + pub fn message_compose_clear(&mut self) { + self.message_compose = TextArea::default(); + self.message_compose.set_block( + Block::default() + .title("Message Compose (send: +)") + .borders(Borders::ALL), + ); + } + + pub fn cli_enable(&mut self) { + self.input_position = InputPosition::CLI; + if self.cli.is_some() { + return; + } + let mut cli = TextArea::default(); + cli.set_block(Block::default().borders(Borders::ALL)); + self.cli = Some(cli); + } + + pub fn cli_disable(&mut self) { + if self.input_position == InputPosition::CLI { + self.cycle_main_input_position(); + } + self.cli = None; + } + + pub async fn update_setup(&mut self) -> Result<()> { + let ui = match &mut self.setup_ui { + Some(c) => c, + None => bail!("SetupUI instance not found"), + }; + + ui.update(&mut self.terminal).await?; + + Ok(()) + } +} diff --git a/src/ui/central/update/mod.rs b/src/ui/central/update/mod.rs new file mode 100644 index 0000000..412cb8c --- /dev/null +++ b/src/ui/central/update/mod.rs @@ -0,0 +1,167 @@ +use std::cmp; + +use anyhow::{Context, Result}; +use tui::{ + layout::{Constraint, Direction, Layout}, + style::Color, +}; + +use crate::{ + app::status::Status, + ui::{textarea_activate, textarea_inactivate}, +}; + +use self::widgets::{messages, room_info, rooms, status}; + +use super::{InputPosition, UI}; + +pub mod widgets; + +impl UI<'_> { + pub async fn update(&mut self, status: &Status) -> Result<()> { + let chunks = match self.cli { + Some(_) => Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Min(10), Constraint::Length(3)].as_ref()) + .split(self.terminal.size()?), + None => vec![self.terminal.size()?], + }; + + let main_chunks = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Length(32), + Constraint::Min(16), + Constraint::Length(32), + ] + .as_ref(), + ) + .split(chunks[0]); + + let left_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Length(5), Constraint::Min(4)].as_ref()) + .split(main_chunks[0]); + + let middle_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Min(4), + Constraint::Length(cmp::min(2 + self.message_compose.lines().len() as u16, 8)), + ] + .as_ref(), + ) + .split(main_chunks[1]); + + let right_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Min(4)].as_ref()) + .split(main_chunks[2]); + + // calculate to widgets colors, based of which widget is currently selected + let colors = match self.input_position { + InputPosition::Status => { + textarea_inactivate(&mut self.message_compose); + if let Some(cli) = &mut self.cli { + textarea_inactivate(cli); + } + vec![ + Color::White, + Color::DarkGray, + Color::DarkGray, + Color::DarkGray, + Color::DarkGray, + ] + } + InputPosition::Rooms => { + textarea_inactivate(&mut self.message_compose); + if let Some(cli) = &mut self.cli { + textarea_inactivate(cli); + } + vec![ + Color::DarkGray, + Color::White, + Color::DarkGray, + Color::DarkGray, + Color::DarkGray, + ] + } + InputPosition::Messages => { + textarea_inactivate(&mut self.message_compose); + if let Some(cli) = &mut self.cli { + textarea_inactivate(cli); + } + vec![ + Color::DarkGray, + Color::DarkGray, + Color::White, + Color::DarkGray, + Color::DarkGray, + ] + } + InputPosition::MessageCompose => { + textarea_activate(&mut self.message_compose); + if let Some(cli) = &mut self.cli { + textarea_inactivate(cli); + } + vec![ + Color::DarkGray, + Color::DarkGray, + Color::DarkGray, + Color::DarkGray, + Color::DarkGray, + ] + } + InputPosition::RoomInfo => { + textarea_inactivate(&mut self.message_compose); + if let Some(cli) = &mut self.cli { + textarea_inactivate(cli); + } + vec![ + Color::DarkGray, + Color::DarkGray, + Color::DarkGray, + Color::DarkGray, + Color::White, + ] + } + InputPosition::CLI => { + textarea_inactivate(&mut self.message_compose); + if let Some(cli) = &mut self.cli { + textarea_activate(cli); + } + vec![ + Color::DarkGray, + Color::DarkGray, + Color::DarkGray, + Color::DarkGray, + Color::DarkGray, + ] + } + }; + + // initiate the widgets + let status_panel = status::init(status, &colors); + let rooms_panel = rooms::init(status, &colors); + let (messages_panel, mut messages_state) = messages::init(status.room(), &colors) + .context("Failed to initiate the messages widget")?; + let room_info_panel = room_info::init(status.room(), &colors); + + // render the widgets + self.terminal.draw(|frame| { + frame.render_widget(status_panel, left_chunks[0]); + frame.render_stateful_widget(rooms_panel, left_chunks[1], &mut self.rooms_state); + frame.render_stateful_widget(messages_panel, middle_chunks[0], &mut messages_state); + frame.render_widget(self.message_compose.widget(), middle_chunks[1]); + match &self.cli { + Some(cli) => frame.render_widget(cli.widget(), chunks[1]), + None => (), + }; + frame.render_widget(room_info_panel, right_chunks[0]); + })?; + + Ok(()) + } +} diff --git a/src/ui/central/update/widgets/messages.rs b/src/ui/central/update/widgets/messages.rs new file mode 100644 index 0000000..0c077b8 --- /dev/null +++ b/src/ui/central/update/widgets/messages.rs @@ -0,0 +1,109 @@ +use anyhow::{Context, Result}; +use matrix_sdk::ruma::events::{AnyMessageLikeEvent, AnyTimelineEvent}; +use tui::{ + layout::Corner, + style::{Color, Modifier, Style}, + text::{Span, Spans, Text}, + widgets::{Block, Borders, List, ListItem, ListState}, +}; + +use crate::{app::status::Room, ui::central::InputPosition}; + +pub fn init<'a>(room: Option<&Room>, colors: &Vec) -> Result<(List<'a>, ListState)> { + let content = match room { + Some(room) => get_content_from_room(room).context("Failed to get content from room")?, + None => vec![ListItem::new(Text::styled( + "No room selected!", + Style::default().fg(Color::Red), + ))], + }; + + let mut messages_state = ListState::default(); + + if let Some(room) = room { + messages_state.select(room.view_scroll()); + } + + Ok(( + List::new(content) + .block( + Block::default() + .title("Messages") + .borders(Borders::ALL) + .style(Style::default().fg(colors[InputPosition::Messages as usize])), + ) + .start_corner(Corner::BottomLeft) + .highlight_symbol(">") + .highlight_style( + Style::default() + .fg(Color::LightMagenta) + .add_modifier(Modifier::BOLD), + ), + messages_state, + )) +} + +fn get_content_from_room(room: &Room) -> Result> { + let results: Vec> = room + .timeline() + .iter() + .rev() + .map(|event| filter_event(event).context("Failed to filter event")) + .collect(); + + let mut output = Vec::with_capacity(results.len()); + for result in results { + output.push(result?); + } + Ok(output) +} + +fn filter_event<'a>(event: &AnyTimelineEvent) -> Result> { + match event { + // Message Like Events + AnyTimelineEvent::MessageLike(message_like_event) => { + let (content, color) = match &message_like_event { + AnyMessageLikeEvent::RoomMessage(room_message_event) => { + let message_content = &room_message_event + .as_original() + .context("Failed to get inner original message_event")? + .content + .body(); + + (message_content.to_string(), Color::White) + } + _ => ( + format!( + "~~~ not supported message like event: {} ~~~", + message_like_event.event_type().to_string() + ), + Color::Red, + ), + }; + let mut text = Text::styled( + message_like_event.sender().to_string(), + Style::default() + .fg(Color::Cyan) + .add_modifier(Modifier::BOLD), + ); + text.extend(Text::styled( + content.to_string(), + Style::default().fg(color), + )); + Ok(ListItem::new(text)) + } + + // State Events + AnyTimelineEvent::State(state) => Ok(ListItem::new(vec![Spans::from(vec![ + Span::styled( + state.sender().to_string(), + Style::default().fg(Color::DarkGray), + ), + Span::styled(": ", Style::default().fg(Color::DarkGray)), + Span::styled( + state.event_type().to_string(), + Style::default().fg(Color::DarkGray), + ), + ])])), + } +} diff --git a/src/ui/central/update/widgets/mod.rs b/src/ui/central/update/widgets/mod.rs new file mode 100644 index 0000000..850c27f --- /dev/null +++ b/src/ui/central/update/widgets/mod.rs @@ -0,0 +1,4 @@ +pub mod messages; +pub mod room_info; +pub mod rooms; +pub mod status; diff --git a/src/ui/central/update/widgets/room_info.rs b/src/ui/central/update/widgets/room_info.rs new file mode 100644 index 0000000..a277e65 --- /dev/null +++ b/src/ui/central/update/widgets/room_info.rs @@ -0,0 +1,36 @@ +use tui::{ + style::{Color, Style}, + text::Text, + widgets::{Block, Borders, Paragraph}, layout::Alignment, +}; + +use crate::{app::status::Room, ui::central::InputPosition}; + +pub fn init<'a>(room: Option<&Room>, colors: &Vec) -> Paragraph<'a> { + let mut room_info_content = Text::default(); + if let Some(room) = room { + room_info_content.extend(Text::styled(room.name(), Style::default().fg(Color::Cyan))); + if room.encrypted() { + room_info_content.extend(Text::styled("Encrypted", Style::default().fg(Color::Green))); + } else { + room_info_content.extend(Text::styled( + "Not Encrypted!", + Style::default().fg(Color::Red), + )); + } + } else { + room_info_content.extend(Text::styled( + "No room selected!", + Style::default().fg(Color::Red), + )); + } + + Paragraph::new(room_info_content) + .block( + Block::default() + .title("Room Info") + .borders(Borders::ALL) + .style(Style::default().fg(colors[InputPosition::RoomInfo as usize])), + ) + .alignment(Alignment::Center) +} diff --git a/src/ui/central/update/widgets/rooms.rs b/src/ui/central/update/widgets/rooms.rs new file mode 100644 index 0000000..4e09a31 --- /dev/null +++ b/src/ui/central/update/widgets/rooms.rs @@ -0,0 +1,29 @@ +use tui::{ + style::{Color, Modifier, Style}, + text::Span, + widgets::{Borders, List, ListItem, Block}, +}; + +use crate::{app::status::Status, ui::central::InputPosition}; + +pub fn init<'a>(status: &Status, colors: &Vec) -> List<'a> { + let rooms_content: Vec<_> = status + .rooms() + .iter() + .map(|(_, room)| ListItem::new(Span::styled(room.name(), Style::default()))) + .collect(); + List::new(rooms_content) + .block( + Block::default() + .title("Rooms (navigate: arrow keys)") + .borders(Borders::ALL) + .style(Style::default().fg(colors[InputPosition::Rooms as usize])), + ) + .style(Style::default().fg(Color::DarkGray)) + .highlight_style( + Style::default() + .fg(Color::Cyan) + .add_modifier(Modifier::BOLD), + ) + .highlight_symbol(">") +} diff --git a/src/ui/central/update/widgets/status.rs b/src/ui/central/update/widgets/status.rs new file mode 100644 index 0000000..108668e --- /dev/null +++ b/src/ui/central/update/widgets/status.rs @@ -0,0 +1,30 @@ +use tui::{ + layout::Alignment, + style::{Color, Modifier, Style}, + text::Text, + widgets::{Block, Borders, Paragraph}, +}; + +use crate::{app::status::Status, ui::central::InputPosition}; + +pub fn init<'a>(status: &Status, colors: &Vec) -> Paragraph<'a> { + let mut status_content = Text::styled( + status.account_name(), + Style::default().add_modifier(Modifier::BOLD), + ); + status_content.extend(Text::styled(status.account_user_id(), Style::default())); + status_content.extend(Text::styled( + "settings", + Style::default() + .fg(Color::LightMagenta) + .add_modifier(Modifier::ITALIC | Modifier::UNDERLINED), + )); + Paragraph::new(status_content) + .block( + Block::default() + .title("Status") + .borders(Borders::ALL) + .style(Style::default().fg(colors[InputPosition::Status as usize])), + ) + .alignment(Alignment::Left) +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index f4d627f..28ebea2 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,64 +1,23 @@ -use std::{cmp, io, io::Stdout}; +pub mod central; +pub mod setup; -use anyhow::{Error, Result}; +use std::{io, io::Stdout}; + +use anyhow::{Context, Result}; use cli_log::info; use crossterm::{ - event::{DisableMouseCapture, EnableMouseCapture}, + event::EnableMouseCapture, execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, + terminal::{enable_raw_mode, EnterAlternateScreen}, }; -use matrix_sdk::ruma::events::{AnyMessageLikeEvent, AnyTimelineEvent}; use tui::{ - backend::CrosstermBackend, - layout::{Alignment, Constraint, Corner, Direction, Layout}, style::{Color, Modifier, Style}, - text::{Span, Spans, Text}, - widgets::{Block, Borders, List, ListItem, ListState, Paragraph}, - Terminal, + widgets::{Block, Borders}, }; use tui_textarea::TextArea; -use crate::app::status::Status; - -#[derive(Clone, Copy)] -pub enum SetupInputPosition { - Homeserver, - Username, - Password, - Ok, -} - -#[derive(Clone, Copy, PartialEq)] -pub enum MainInputPosition { - Status, - Rooms, - Messages, - MessageCompose, - RoomInfo, - CLI, -} - -pub struct SetupUI<'a> { - input_position: SetupInputPosition, - - pub homeserver: TextArea<'a>, - pub username: TextArea<'a>, - pub password: TextArea<'a>, - pub password_data: TextArea<'a>, -} - -pub struct UI<'a> { - terminal: Terminal>, - input_position: MainInputPosition, - pub rooms_state: ListState, - pub message_compose: TextArea<'a>, - pub cli: Option>, - - pub setup_ui: Option>, -} - fn terminal_prepare() -> Result { - enable_raw_mode()?; + enable_raw_mode().context("Failed to enable raw mode")?; let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; info!("Prepared terminal"); @@ -84,553 +43,3 @@ pub fn textarea_inactivate(textarea: &mut TextArea) { .unwrap_or_else(|| Block::default().borders(Borders::ALL)); textarea.set_block(b.style(Style::default().fg(Color::DarkGray))); } - -impl Drop for UI<'_> { - fn drop(&mut self) { - info!("Destructing UI"); - disable_raw_mode().expect("While destructing UI -> Failed to disable raw mode"); - execute!( - self.terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - ).expect("While destructing UI -> Failed execute backend commands (LeaveAlternateScreen and DisableMouseCapture)"); - self.terminal - .show_cursor() - .expect("While destructing UI -> Failed to re-enable cursor"); - } -} - -impl SetupUI<'_> { - pub fn new() -> Self { - let mut homeserver = TextArea::new(vec!["https://matrix.org".to_string()]); - let mut username = TextArea::default(); - let mut password = TextArea::default(); - let password_data = TextArea::default(); - - homeserver.set_block(Block::default().title("Homeserver").borders(Borders::ALL)); - username.set_block(Block::default().title("Username").borders(Borders::ALL)); - password.set_block(Block::default().title("Password").borders(Borders::ALL)); - - textarea_activate(&mut homeserver); - textarea_inactivate(&mut username); - textarea_inactivate(&mut password); - - Self { - input_position: SetupInputPosition::Homeserver, - homeserver, - username, - password, - password_data, - } - } - - pub fn cycle_input_position(&mut self) { - self.input_position = match self.input_position { - SetupInputPosition::Homeserver => { - textarea_inactivate(&mut self.homeserver); - textarea_activate(&mut self.username); - textarea_inactivate(&mut self.password); - SetupInputPosition::Username - } - SetupInputPosition::Username => { - textarea_inactivate(&mut self.homeserver); - textarea_inactivate(&mut self.username); - textarea_activate(&mut self.password); - SetupInputPosition::Password - } - SetupInputPosition::Password => { - textarea_inactivate(&mut self.homeserver); - textarea_inactivate(&mut self.username); - textarea_inactivate(&mut self.password); - SetupInputPosition::Ok - } - SetupInputPosition::Ok => { - textarea_activate(&mut self.homeserver); - textarea_inactivate(&mut self.username); - textarea_inactivate(&mut self.password); - SetupInputPosition::Homeserver - } - }; - } - - pub fn cycle_input_position_rev(&mut self) { - self.input_position = match self.input_position { - SetupInputPosition::Homeserver => { - textarea_inactivate(&mut self.homeserver); - textarea_inactivate(&mut self.username); - textarea_inactivate(&mut self.password); - SetupInputPosition::Ok - } - SetupInputPosition::Username => { - textarea_activate(&mut self.homeserver); - textarea_inactivate(&mut self.username); - textarea_inactivate(&mut self.password); - SetupInputPosition::Homeserver - } - SetupInputPosition::Password => { - textarea_inactivate(&mut self.homeserver); - textarea_activate(&mut self.username); - textarea_inactivate(&mut self.password); - SetupInputPosition::Username - } - SetupInputPosition::Ok => { - textarea_inactivate(&mut self.homeserver); - textarea_inactivate(&mut self.username); - textarea_activate(&mut self.password); - SetupInputPosition::Password - } - }; - } - - pub fn input_position(&self) -> &SetupInputPosition { - &self.input_position - } - - pub async fn update( - &'_ mut self, - terminal: &mut Terminal>, - ) -> Result<()> { - let mut strings: Vec = Vec::new(); - strings.resize(3, "".to_string()); - - let content_ok = match self.input_position { - SetupInputPosition::Ok => { - Span::styled("OK", Style::default().add_modifier(Modifier::UNDERLINED)) - } - _ => Span::styled("OK", Style::default().fg(Color::DarkGray)), - }; - - let block = Block::default().title("Login").borders(Borders::ALL); - - let ok = Paragraph::new(content_ok).alignment(Alignment::Center); - - // define a 32 * 6 chunk in the middle of the screen - let mut chunk = terminal.size()?; - chunk.x = (chunk.width / 2) - 16; - chunk.y = (chunk.height / 2) - 5; - chunk.height = 12; - chunk.width = 32; - - let mut split_chunk = chunk.clone(); - split_chunk.x += 1; - split_chunk.y += 1; - split_chunk.height -= 1; - split_chunk.width -= 2; - - let chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Length(3), // 0. Homserver: - Constraint::Length(3), // 1. Username: - Constraint::Length(3), // 2. Password: - Constraint::Length(1), // 3. OK - ] - .as_ref(), - ) - .split(split_chunk); - - terminal.draw(|frame| { - frame.render_widget(block.clone(), chunk); - frame.render_widget(self.homeserver.widget(), chunks[0]); - frame.render_widget(self.username.widget(), chunks[1]); - frame.render_widget(self.password.widget(), chunks[2]); - frame.render_widget(ok.clone(), chunks[3]); - })?; - - Ok(()) - } -} - -impl UI<'_> { - pub fn new() -> Result { - let stdout = terminal_prepare()?; - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - - terminal.clear()?; - - let mut message_compose = TextArea::default(); - message_compose.set_block( - Block::default() - .title("Message Compose (send: +)") - .borders(Borders::ALL), - ); - - info!("Initialized UI"); - - Ok(Self { - terminal, - input_position: MainInputPosition::Rooms, - rooms_state: ListState::default(), - message_compose, - cli: None, - setup_ui: None, - }) - } - - pub fn cycle_main_input_position(&mut self) { - self.input_position = match self.input_position { - MainInputPosition::Status => MainInputPosition::Rooms, - MainInputPosition::Rooms => MainInputPosition::Messages, - MainInputPosition::Messages => MainInputPosition::MessageCompose, - MainInputPosition::MessageCompose => MainInputPosition::RoomInfo, - MainInputPosition::RoomInfo => match self.cli { - Some(_) => MainInputPosition::CLI, - None => MainInputPosition::Status, - }, - MainInputPosition::CLI => MainInputPosition::Status, - }; - } - - pub fn cycle_main_input_position_rev(&mut self) { - self.input_position = match self.input_position { - MainInputPosition::Status => match self.cli { - Some(_) => MainInputPosition::CLI, - None => MainInputPosition::RoomInfo, - }, - MainInputPosition::Rooms => MainInputPosition::Status, - MainInputPosition::Messages => MainInputPosition::Rooms, - MainInputPosition::MessageCompose => MainInputPosition::Messages, - MainInputPosition::RoomInfo => MainInputPosition::MessageCompose, - MainInputPosition::CLI => MainInputPosition::RoomInfo, - }; - } - - pub fn input_position(&self) -> &MainInputPosition { - &self.input_position - } - - pub fn message_compose_clear(&mut self) { - self.message_compose = TextArea::default(); - self.message_compose.set_block( - Block::default() - .title("Message Compose (send: +)") - .borders(Borders::ALL), - ); - } - - pub fn cli_enable(&mut self) { - self.input_position = MainInputPosition::CLI; - if self.cli.is_some() { - return; - } - let mut cli = TextArea::default(); - cli.set_block(Block::default().borders(Borders::ALL)); - self.cli = Some(cli); - } - - pub fn cli_disable(&mut self) { - if self.input_position == MainInputPosition::CLI { - self.cycle_main_input_position(); - } - self.cli = None; - } - - pub async fn update(&mut self, status: &Status) -> Result<()> { - let chunks = match self.cli { - Some(_) => Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Min(10), Constraint::Length(3)].as_ref()) - .split(self.terminal.size()?), - None => vec![self.terminal.size()?], - }; - - let main_chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Length(32), - Constraint::Min(16), - Constraint::Length(32), - ] - .as_ref(), - ) - .split(chunks[0]); - - let left_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Length(5), Constraint::Min(4)].as_ref()) - .split(main_chunks[0]); - - let middle_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Min(4), - Constraint::Length(cmp::min(2 + self.message_compose.lines().len() as u16, 8)), - ] - .as_ref(), - ) - .split(main_chunks[1]); - - let right_chunks = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Min(4)].as_ref()) - .split(main_chunks[2]); - - let mut status_content = Text::styled( - status.account_name(), - Style::default().add_modifier(Modifier::BOLD), - ); - status_content.extend(Text::styled(status.account_user_id(), Style::default())); - status_content.extend(Text::styled( - "settings", - Style::default() - .fg(Color::LightMagenta) - .add_modifier(Modifier::ITALIC | Modifier::UNDERLINED), - )); - - let rooms_content = status - .rooms() - .iter() - .map(|(_, room)| ListItem::new(Span::styled(room.name(), Style::default()))) - .collect::>(); - - let messages_content = match status.room() { - Some(r) => { - r.timeline() - .iter() - .rev() - .map(|event| { - match event { - // Message Like Events - AnyTimelineEvent::MessageLike(message_like_event) => { - let (content, color) = match &message_like_event { - AnyMessageLikeEvent::RoomMessage(room_message_event) => { - let message_content = &room_message_event - .as_original() - .unwrap() - .content - .body(); - - (message_content.to_string(), Color::White) - } - _ => ( - format!( - "~~~ not supported message like event: {} ~~~", - message_like_event.event_type().to_string() - ), - Color::Red, - ), - }; - let mut text = Text::styled( - message_like_event.sender().to_string(), - Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD), - ); - text.extend(Text::styled( - content.to_string(), - Style::default().fg(color), - )); - ListItem::new(text) - } - - // State Events - AnyTimelineEvent::State(state) => { - ListItem::new(vec![Spans::from(vec![ - Span::styled( - state.sender().to_string(), - Style::default().fg(Color::DarkGray), - ), - Span::styled(": ", Style::default().fg(Color::DarkGray)), - Span::styled( - state.event_type().to_string(), - Style::default().fg(Color::DarkGray), - ), - ])]) - } - } - }) - .collect::>() - } - None => { - vec![ListItem::new(Text::styled( - "No room selected!", - Style::default().fg(Color::Red), - ))] - } - }; - - let mut messages_state = ListState::default(); - let mut room_info_content = Text::default(); - - if let Some(room) = status.room() { - messages_state.select(room.view_scroll()); - - room_info_content.extend(Text::styled(room.name(), Style::default().fg(Color::Cyan))); - if room.encrypted() { - room_info_content - .extend(Text::styled("Encrypted", Style::default().fg(Color::Green))); - } else { - room_info_content.extend(Text::styled( - "Not Encrypted!", - Style::default().fg(Color::Red), - )); - } - } else { - room_info_content.extend(Text::styled( - "No room selected!", - Style::default().fg(Color::Red), - )); - } - - // calculate to widgets colors, based of which widget is currently selected - let colors = match self.input_position { - MainInputPosition::Status => { - textarea_inactivate(&mut self.message_compose); - if let Some(cli) = &mut self.cli { - textarea_inactivate(cli); - } - vec![ - Color::White, - Color::DarkGray, - Color::DarkGray, - Color::DarkGray, - Color::DarkGray, - ] - } - MainInputPosition::Rooms => { - textarea_inactivate(&mut self.message_compose); - if let Some(cli) = &mut self.cli { - textarea_inactivate(cli); - } - vec![ - Color::DarkGray, - Color::White, - Color::DarkGray, - Color::DarkGray, - Color::DarkGray, - ] - } - MainInputPosition::Messages => { - textarea_inactivate(&mut self.message_compose); - if let Some(cli) = &mut self.cli { - textarea_inactivate(cli); - } - vec![ - Color::DarkGray, - Color::DarkGray, - Color::White, - Color::DarkGray, - Color::DarkGray, - ] - } - MainInputPosition::MessageCompose => { - textarea_activate(&mut self.message_compose); - if let Some(cli) = &mut self.cli { - textarea_inactivate(cli); - } - vec![ - Color::DarkGray, - Color::DarkGray, - Color::DarkGray, - Color::DarkGray, - Color::DarkGray, - ] - } - MainInputPosition::RoomInfo => { - textarea_inactivate(&mut self.message_compose); - if let Some(cli) = &mut self.cli { - textarea_inactivate(cli); - } - vec![ - Color::DarkGray, - Color::DarkGray, - Color::DarkGray, - Color::DarkGray, - Color::White, - ] - } - MainInputPosition::CLI => { - textarea_inactivate(&mut self.message_compose); - if let Some(cli) = &mut self.cli { - textarea_activate(cli); - } - vec![ - Color::DarkGray, - Color::DarkGray, - Color::DarkGray, - Color::DarkGray, - Color::DarkGray, - ] - } - }; - - // initiate the widgets - let status_panel = Paragraph::new(status_content) - .block( - Block::default() - .title("Status") - .borders(Borders::ALL) - .style(Style::default().fg(colors[MainInputPosition::Status as usize])), - ) - .alignment(Alignment::Left); - - let rooms_panel = List::new(rooms_content) - .block( - Block::default() - .title("Rooms (navigate: arrow keys)") - .borders(Borders::ALL) - .style(Style::default().fg(colors[MainInputPosition::Rooms as usize])), - ) - .style(Style::default().fg(Color::DarkGray)) - .highlight_style( - Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD), - ) - .highlight_symbol(">"); - - let messages_panel = List::new(messages_content) - .block( - Block::default() - .title("Messages") - .borders(Borders::ALL) - .style(Style::default().fg(colors[MainInputPosition::Messages as usize])), - ) - .start_corner(Corner::BottomLeft) - .highlight_symbol(">") - .highlight_style( - Style::default() - .fg(Color::LightMagenta) - .add_modifier(Modifier::BOLD), - ); - - let room_info_panel = Paragraph::new(room_info_content) - .block( - Block::default() - .title("Room Info") - .borders(Borders::ALL) - .style(Style::default().fg(colors[MainInputPosition::RoomInfo as usize])), - ) - .alignment(Alignment::Center); - - // render the widgets - self.terminal.draw(|frame| { - frame.render_widget(status_panel, left_chunks[0]); - frame.render_stateful_widget(rooms_panel, left_chunks[1], &mut self.rooms_state); - frame.render_stateful_widget(messages_panel, middle_chunks[0], &mut messages_state); - frame.render_widget(self.message_compose.widget(), middle_chunks[1]); - match &self.cli { - Some(cli) => frame.render_widget(cli.widget(), chunks[1]), - None => (), - }; - frame.render_widget(room_info_panel, right_chunks[0]); - })?; - - Ok(()) - } - - pub async fn update_setup(&mut self) -> Result<()> { - let ui = match &mut self.setup_ui { - Some(c) => c, - None => return Err(Error::msg("SetupUI instance not found")), - }; - - ui.update(&mut self.terminal).await?; - - Ok(()) - } -} diff --git a/src/ui/setup.rs b/src/ui/setup.rs new file mode 100644 index 0000000..da339b2 --- /dev/null +++ b/src/ui/setup.rs @@ -0,0 +1,172 @@ +use std::io::Stdout; + +use anyhow::Result; +use tui::{ + backend::CrosstermBackend, + layout::{Constraint, Direction, Layout, Alignment}, + style::{Color, Modifier, Style}, + text::Span, + widgets::{Block, Borders, Paragraph}, + Terminal, +}; +use tui_textarea::TextArea; + +use crate::ui::{textarea_activate, textarea_inactivate}; + +pub struct UI<'a> { + input_position: InputPosition, + + pub homeserver: TextArea<'a>, + pub username: TextArea<'a>, + pub password: TextArea<'a>, + pub password_data: TextArea<'a>, +} + +#[derive(Clone, Copy)] +pub enum InputPosition { + Homeserver, + Username, + Password, + Ok, +} + +impl UI<'_> { + pub fn new() -> Self { + let mut homeserver = TextArea::new(vec!["https://matrix.org".to_string()]); + let mut username = TextArea::default(); + let mut password = TextArea::default(); + let password_data = TextArea::default(); + + homeserver.set_block(Block::default().title("Homeserver").borders(Borders::ALL)); + username.set_block(Block::default().title("Username").borders(Borders::ALL)); + password.set_block(Block::default().title("Password").borders(Borders::ALL)); + + textarea_activate(&mut homeserver); + textarea_inactivate(&mut username); + textarea_inactivate(&mut password); + + Self { + input_position: InputPosition::Homeserver, + homeserver, + username, + password, + password_data, + } + } + + pub fn cycle_input_position(&mut self) { + self.input_position = match self.input_position { + InputPosition::Homeserver => { + textarea_inactivate(&mut self.homeserver); + textarea_activate(&mut self.username); + textarea_inactivate(&mut self.password); + InputPosition::Username + } + InputPosition::Username => { + textarea_inactivate(&mut self.homeserver); + textarea_inactivate(&mut self.username); + textarea_activate(&mut self.password); + InputPosition::Password + } + InputPosition::Password => { + textarea_inactivate(&mut self.homeserver); + textarea_inactivate(&mut self.username); + textarea_inactivate(&mut self.password); + InputPosition::Ok + } + InputPosition::Ok => { + textarea_activate(&mut self.homeserver); + textarea_inactivate(&mut self.username); + textarea_inactivate(&mut self.password); + InputPosition::Homeserver + } + }; + } + + pub fn cycle_input_position_rev(&mut self) { + self.input_position = match self.input_position { + InputPosition::Homeserver => { + textarea_inactivate(&mut self.homeserver); + textarea_inactivate(&mut self.username); + textarea_inactivate(&mut self.password); + InputPosition::Ok + } + InputPosition::Username => { + textarea_activate(&mut self.homeserver); + textarea_inactivate(&mut self.username); + textarea_inactivate(&mut self.password); + InputPosition::Homeserver + } + InputPosition::Password => { + textarea_inactivate(&mut self.homeserver); + textarea_activate(&mut self.username); + textarea_inactivate(&mut self.password); + InputPosition::Username + } + InputPosition::Ok => { + textarea_inactivate(&mut self.homeserver); + textarea_inactivate(&mut self.username); + textarea_activate(&mut self.password); + InputPosition::Password + } + }; + } + + pub fn input_position(&self) -> &InputPosition { + &self.input_position + } + + pub async fn update( + &mut self, + terminal: &mut Terminal>, + ) -> Result<()> { + let strings: Vec = vec!["".to_owned(); 3]; + + let content_ok = match self.input_position { + InputPosition::Ok => { + Span::styled("OK", Style::default().add_modifier(Modifier::UNDERLINED)) + } + _ => Span::styled("OK", Style::default().fg(Color::DarkGray)), + }; + + let block = Block::default().title("Login").borders(Borders::ALL); + + let ok = Paragraph::new(content_ok).alignment(Alignment::Center); + + // define a 32 * 6 chunk in the middle of the screen + let mut chunk = terminal.size()?; + chunk.x = (chunk.width / 2) - 16; + chunk.y = (chunk.height / 2) - 5; + chunk.height = 12; + chunk.width = 32; + + let mut split_chunk = chunk.clone(); + split_chunk.x += 1; + split_chunk.y += 1; + split_chunk.height -= 1; + split_chunk.width -= 2; + + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Length(3), // 0. Homserver: + Constraint::Length(3), // 1. Username: + Constraint::Length(3), // 2. Password: + Constraint::Length(1), // 3. OK + ] + .as_ref(), + ) + .split(split_chunk); + + terminal.draw(|frame| { + frame.render_widget(block.clone(), chunk); + frame.render_widget(self.homeserver.widget(), chunks[0]); + frame.render_widget(self.username.widget(), chunks[1]); + frame.render_widget(self.password.widget(), chunks[2]); + frame.render_widget(ok.clone(), chunks[3]); + })?; + + Ok(()) + } +} -- 2.40.1 From 3e8722433d348bb18ae5cd2ad08fc88ce5bb0303 Mon Sep 17 00:00:00 2001 From: Soispha Date: Sun, 16 Jul 2023 14:01:48 +0200 Subject: [PATCH 02/18] Fix(lua_macros): Expand to generate the required types and functions --- lua_macros/Cargo.toml | 5 +- lua_macros/src/generate_noop_lua_function.rs | 11 ++ lua_macros/src/lib.rs | 99 ++++++------ lua_macros/src/mark_as_ci_command.rs | 161 +++++++++++++++++++ lua_macros/src/struct_to_ci_enum.rs | 140 ++++++++++++++++ 5 files changed, 369 insertions(+), 47 deletions(-) create mode 100644 lua_macros/src/generate_noop_lua_function.rs create mode 100644 lua_macros/src/mark_as_ci_command.rs create mode 100644 lua_macros/src/struct_to_ci_enum.rs diff --git a/lua_macros/Cargo.toml b/lua_macros/Cargo.toml index bf1583b..cb754e2 100644 --- a/lua_macros/Cargo.toml +++ b/lua_macros/Cargo.toml @@ -4,9 +4,10 @@ version = "0.1.0" edition = "2021" [lib] -crate_type = ["proc-macro"] +proc-macro = true [dependencies] +convert_case = "0.6.0" proc-macro2 = "1.0.64" quote = "1.0.29" -syn = "2.0.25" +syn = { version = "2.0.25", features = ["extra-traits", "full", "parsing"] } diff --git a/lua_macros/src/generate_noop_lua_function.rs b/lua_macros/src/generate_noop_lua_function.rs new file mode 100644 index 0000000..eac9e0a --- /dev/null +++ b/lua_macros/src/generate_noop_lua_function.rs @@ -0,0 +1,11 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::ToTokens; + +// TODO: Do we need this noop? +pub fn generate_default_lua_function(input: &syn::Field) -> TokenStream2 { + let output: TokenStream2 = syn::parse(input.into_token_stream().into()) + .expect("This is generated from valid rust code, it should stay that way."); + + output +} + diff --git a/lua_macros/src/lib.rs b/lua_macros/src/lib.rs index 777665e..12fe422 100644 --- a/lua_macros/src/lib.rs +++ b/lua_macros/src/lib.rs @@ -1,59 +1,68 @@ +mod generate_noop_lua_function; +mod mark_as_ci_command; +mod struct_to_ci_enum; + +use generate_noop_lua_function::generate_default_lua_function; +use mark_as_ci_command::generate_final_function; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; -use quote::{format_ident, quote}; -use syn; +use quote::quote; +use struct_to_ci_enum::{generate_command_enum, generate_generate_ci_function}; +use syn::{self, ItemFn, Field, parse::Parser}; #[proc_macro_attribute] -pub fn generate_ci_functions(_: TokenStream, input: TokenStream) -> TokenStream { +pub fn turn_struct_to_ci_commands(_attrs: TokenStream, input: TokenStream) -> TokenStream { // Construct a representation of Rust code as a syntax tree // that we can manipulate let input = syn::parse(input) .expect("This should always be valid rust code, as it's extracted from direct code"); // Build the trait implementation - generate_generate_ci_functions(&input) + let generate_ci_function: TokenStream2 = generate_generate_ci_function(&input); + + let command_enum = generate_command_enum(&input); + + quote! { + #command_enum + #generate_ci_function + } + .into() } -fn generate_generate_ci_functions(input: &syn::DeriveInput) -> TokenStream { - let input_tokens: TokenStream2 = match &input.data { - syn::Data::Struct(input) => match &input.fields { - syn::Fields::Named(named_fields) => named_fields - .named - .iter() - .map(|field| -> TokenStream2 { - let field_ident = field.ident.as_ref().expect( - "These are only the named field, thus they all should have a name.", - ); - let function_name_ident = format_ident!("fun_{}", field_ident); - let function_name = format!("{}", field_ident); - quote! { - let #function_name_ident = context.create_function(#field_ident).expect( - &format!( - "The function: `{}` should be defined", - #function_name - ) - ); - globals.set(#function_name, #function_name_ident).expect( - &format!( - "Setting a static global value ({}, fun_{}) should work", - #function_name, - #function_name - ) - ); - } - .into() - }) - .collect(), - _ => unimplemented!("Only implemented for named fileds"), - }, - _ => unimplemented!("Only for implemented for structs"), - }; +/// Generate a default lua function implementation. +#[proc_macro_attribute] +pub fn gen_lua_function(_attrs: TokenStream, input: TokenStream) -> TokenStream { + // Construct a representation of Rust code as a syntax tree + // that we can manipulate + // + let parser = Field::parse_named; + let input = parser.parse(input) + .expect("This is only defined for named fileds."); - let gen = quote! { - pub fn generate_ci_functions(context: &mut Context) { - let globals = context.globals(); - #input_tokens - } - }; - gen.into() + + // Build the trait implementation + let default_lua_function: TokenStream2 = generate_default_lua_function(&input); + + quote! { + #default_lua_function + } + .into() +} + +/// Turn a function into a valid ci command function +#[proc_macro_attribute] +pub fn ci_command(_attrs: TokenStream, input: TokenStream) -> TokenStream { + // Construct a representation of Rust code as a syntax tree + // that we can manipulate + let mut input: ItemFn = syn::parse(input) + .expect("This should always be valid rust code, as it's extracted from direct code"); + + // Build the trait implementation + let output_function: TokenStream2 = generate_final_function(&mut input); + + //panic!("{:#?}", output_function); + quote! { + #output_function + } + .into() } diff --git a/lua_macros/src/mark_as_ci_command.rs b/lua_macros/src/mark_as_ci_command.rs new file mode 100644 index 0000000..08b715a --- /dev/null +++ b/lua_macros/src/mark_as_ci_command.rs @@ -0,0 +1,161 @@ +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote, ToTokens}; +use syn::{Block, Expr, ExprBlock, GenericArgument, ReturnType, Stmt, Type}; + +pub fn generate_final_function(input: &mut syn::ItemFn) -> TokenStream2 { + append_tx_send_code(input); + + let output: TokenStream2 = syn::parse(input.into_token_stream().into()) + .expect("This is generated from valid rust code, it should stay that way."); + + output +} + +fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn { + let function_name_pascal = format_ident!( + "{}", + input + .sig + .ident + .clone() + .to_string() + .from_case(Case::Snake) + .to_case(Case::Pascal) + ); + + let tx_send = match &input.sig.output { + syn::ReturnType::Default => { + todo!( + "Does this case even trigger? All functions should have a output of (Result<$type, rlua::Error>)" + ); + quote! { + { + let tx: std::sync::mpsc::Sender = + context + .named_registry_value("sender_for_ci_commands") + .expect("This exists, it was set before"); + + tx + .send(Event::CommandEvent(Command::#function_name_pascal)) + .expect("This should work, as the reciever is not dropped"); + } + } + } + syn::ReturnType::Type(_, ret_type) => { + let return_type = match *(ret_type.clone()) { + syn::Type::Path(path) => { + match path + .path + .segments + .first() + .expect("This is expected to be only one path segment") + .arguments + .to_owned() + { + syn::PathArguments::AngleBracketed(angled_path) => { + let angled_path = angled_path.args.to_owned(); + let filtered_paths: Vec<_> = angled_path + .into_iter() + .filter(|generic_arg| { + if let GenericArgument::Type(generic_type) = generic_arg { + if let Type::Path(_) = generic_type { + true + } else { + false + } + } else { + false + } + }) + .collect(); + + // There should only be two segments (the type is ) + if filtered_paths.len() > 2 { + unreachable!( + "There should be no more than two filtered_output, but got: {:#?}", + filtered_paths + ) + } else if filtered_paths.len() <= 0 { + unreachable!( + "There should be more than zero filtered_output, but got: {:#?}", + filtered_paths + ) + } + if filtered_paths.len() == 2 { + // There is something else than rlua + let gen_type = if let GenericArgument::Type(ret_type) = + filtered_paths + .first() + .expect("One path segment should exists") + .to_owned() + { + ret_type + } else { + unreachable!("These were filtered above."); + }; + let return_type_as_type_prepared = quote! {-> #gen_type}; + + let return_type_as_return_type: ReturnType = syn::parse( + return_type_as_type_prepared.to_token_stream().into(), + ) + .expect("This is valid."); + return_type_as_return_type + } else { + // There is only rlua + ReturnType::Default + } + } + _ => unimplemented!("Only for angled paths"), + } + } + _ => unimplemented!("Only for path types"), + }; + match return_type { + ReturnType::Default => { + quote! { + { + let tx: std::sync::mpsc::Sender = + context + .named_registry_value("sender_for_ci_commands") + .expect("This exists, it was set before"); + + tx + .send(Event::CommandEvent(Command::#function_name_pascal)) + .expect("This should work, as the reciever is not dropped"); + } + } + } + ReturnType::Type(_, _) => { + quote! { + { + let tx: std::sync::mpsc::Sender = + context + .named_registry_value("sender_for_ci_commands") + .expect("This exists, it was set before"); + + tx + .send(Event::CommandEvent(Command::#function_name_pascal(input_str))) + .expect("This should work, as the reciever is not dropped"); + } + } + } + } + } + }; + + let tx_send_block: Block = + syn::parse(tx_send.into()).expect("This is a static string, it will always parse"); + let tx_send_expr_block = ExprBlock { + attrs: vec![], + label: None, + block: tx_send_block, + }; + let mut tx_send_stmt = vec![Stmt::Expr(Expr::Block(tx_send_expr_block), None)]; + + let mut new_stmts: Vec = Vec::with_capacity(input.block.stmts.len() + 1); + new_stmts.append(&mut tx_send_stmt); + new_stmts.append(&mut input.block.stmts); + input.block.stmts = new_stmts; + input +} diff --git a/lua_macros/src/struct_to_ci_enum.rs b/lua_macros/src/struct_to_ci_enum.rs new file mode 100644 index 0000000..f231249 --- /dev/null +++ b/lua_macros/src/struct_to_ci_enum.rs @@ -0,0 +1,140 @@ +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{self, ReturnType}; + +pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 { + let mut functions_to_generate: Vec = vec![]; + let input_tokens: TokenStream2 = match &input.data { + syn::Data::Struct(input) => match &input.fields { + syn::Fields::Named(named_fields) => named_fields + .named + .iter() + .map(|field| -> TokenStream2 { + if field.attrs.iter().any(|attribute| { + attribute.path() + == &syn::parse_str::("gen_default_lua_function") + .expect("This is valid rust code") + }) { + let function_name = field + .ident + .as_ref() + .expect("These are only the named field, thus they all should have a name."); + functions_to_generate.push(quote! { + #[ci_command] + fn #function_name(context: Context, input_str: String) -> Result<(), rlua::Error> { + Ok(()) + } + }); + generate_ci_part(field) + } else { + generate_ci_part(field) + } + }) + .collect(), + + _ => unimplemented!("Only implemented for named fileds"), + }, + _ => unimplemented!("Only implemented for structs"), + }; + + let functions_to_generate: TokenStream2 = functions_to_generate.into_iter().collect(); + let gen = quote! { + pub fn generate_ci_functions( + context: &mut rlua::Context, + tx: std::sync::mpsc::Sender) + { + context.set_named_registry_value("sender_for_ci_commands", tx).expect("This should always work, as the value is added before all else"); + let globals = context.globals(); + #input_tokens + } + #functions_to_generate + }; + gen.into() +} + +fn generate_ci_part(field: &syn::Field) -> TokenStream2 { + let field_ident = field + .ident + .as_ref() + .expect("These are only the named field, thus they all should have a name."); + let function_name_ident = format_ident!("fun_{}", field_ident); + let function_name = format!("{}", field_ident); + quote! { + let #function_name_ident = context.create_function(#field_ident).expect( + &format!( + "The function: `{}` should be defined", + #function_name + ) + ); + + globals.set(#function_name, #function_name_ident).expect( + &format!( + "Setting a static global value ({}, fun_{}) should work", + #function_name, + #function_name + ) + ); + } + .into() +} + +pub fn generate_command_enum(input: &syn::DeriveInput) -> TokenStream2 { + let input_tokens: TokenStream2 = match &input.data { + syn::Data::Struct(input) => match &input.fields { + syn::Fields::Named(named_fields) => named_fields + .named + .iter() + .map(|field| -> TokenStream2 { + let field_ident = field + .ident + .as_ref() + .expect("These are only the named field, thus they all should have a name."); + + let enum_variant_type = match &field.ty { + syn::Type::BareFn(function) => { + let return_path: &ReturnType = &function.output; + match return_path { + ReturnType::Default => None, + ReturnType::Type(_, return_type) => Some(match *(return_type.to_owned()) { + syn::Type::Path(path_type) => path_type + .path + .get_ident() + .expect("A path should either be complete, or only conain one segment") + .to_owned(), + _ => unimplemented!("This is only implemented for path types"), + }), + } + } + _ => unimplemented!("This is only implemented for bare function types"), + }; + + let enum_variant_name = format_ident!( + "{}", + field_ident.to_string().from_case(Case::Snake).to_case(Case::Pascal) + ); + if enum_variant_type.is_some() { + quote! { + #enum_variant_name (#enum_variant_type), + } + .into() + } else { + quote! { + #enum_variant_name, + } + } + }) + .collect(), + _ => unimplemented!("Only implemented for named fileds"), + }, + _ => unimplemented!("Only implemented for structs"), + }; + + let gen = quote! { + #[derive(Debug)] + pub enum Command { + #input_tokens + } + }; + gen.into() +} -- 2.40.1 From ba225e29df778ac8085a33004a2f5d234e88cee1 Mon Sep 17 00:00:00 2001 From: Soispha Date: Sun, 16 Jul 2023 14:13:58 +0200 Subject: [PATCH 03/18] Fix(treewide): Use the new lua_macros macros --- src/app/command.rs | 25 --------- src/app/command_interface.rs | 53 ++++++++++++++++--- .../event_types/event/handlers/command.rs | 2 +- src/app/events/event_types/event/mod.rs | 2 +- src/app/mod.rs | 9 ++-- 5 files changed, 52 insertions(+), 39 deletions(-) delete mode 100644 src/app/command.rs diff --git a/src/app/command.rs b/src/app/command.rs deleted file mode 100644 index 6342bf9..0000000 --- a/src/app/command.rs +++ /dev/null @@ -1,25 +0,0 @@ -use anyhow::Result; -use tokio::sync::mpsc; - -use super::events::event_types::Event; - -#[derive(Debug, Clone)] -pub enum Command { - // Closes the application - Exit, - - CommandLineShow, - CommandLineHide, - - CyclePlanes, - CyclePlanesRev, - - // sends a message to the current room - RoomMessageSend(String), -} - -pub async fn execute(channel: &mpsc::Sender, command: Command) -> Result<()> { - let event = Event::CommandEvent(command); - channel.send(event).await?; - Ok(()) -} diff --git a/src/app/command_interface.rs b/src/app/command_interface.rs index e38e91e..87f184d 100644 --- a/src/app/command_interface.rs +++ b/src/app/command_interface.rs @@ -1,13 +1,52 @@ -use lua_macros::generate_ci_functions; +// FIXME: This file needs documentation with examples of how the proc macros work. +use lua_macros::{ci_command, turn_struct_to_ci_commands}; use rlua::Context; -// This struct is here to gurantee, 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. -#[generate_ci_functions()] +use super::events::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. +/// +/// What it does: +/// - Generates a `generate_ci_functions` function, which wraps the specified rust in functions +/// in lua and exports them to the globals in the context provided as argument. +/// - Generates a Commands enum, which contains every Camel cased version of the fields. +/// +/// Every command specified here should have a function named $command_name, where $command_name is the snake cased name of the field. +/// +/// This function is exported to the lua context, thus it's signature must be: +/// ```rust +/// fn $command_name(context: Context, input_string: String) -> Result<$return_type, rlua::Error> {} +/// ``` +/// where $return_type is the type returned by the function (the only supported ones are right now +/// `String` and `()`). +#[turn_struct_to_ci_commands] struct Commands<'lua> { - greet: Function<'lua>, + greet: fn(usize) -> String, + + // Closes the application + #[gen_default_lua_function] + exit: fn(), + #[gen_default_lua_function] + command_line_show: fn(), + #[gen_default_lua_function] + command_line_hide: fn(), + + #[gen_default_lua_function] + cycle_planes: fn(), + #[gen_default_lua_function] + cycle_planes_rev: fn(), + + //// sends a message to the current room + room_message_send: fn(String) -> String, } -fn greet(context: Context, name: String) -> Result { - Ok(format!("Name is {}", name)) +#[ci_command] +fn greet(context: Context, input_str: String) -> Result { + Ok(format!("Name is {}", input_str)) +} + +#[ci_command] +fn room_message_send(context: Context, 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 0c67ed2..364ad23 100644 --- a/src/app/events/event_types/event/handlers/command.rs +++ b/src/app/events/event_types/event/handlers/command.rs @@ -1,4 +1,4 @@ -use crate::app::{command::Command, events::event_types::EventStatus, App}; +use crate::app::{events::event_types::EventStatus, App, command_interface::Command}; use anyhow::Result; use cli_log::info; diff --git a/src/app/events/event_types/event/mod.rs b/src/app/events/event_types/event/mod.rs index ffdad06..b37d452 100644 --- a/src/app/events/event_types/event/mod.rs +++ b/src/app/events/event_types/event/mod.rs @@ -3,7 +3,7 @@ mod handlers; use anyhow::{Context, Result}; use crossterm::event::Event as CrosstermEvent; -use crate::app::{command::Command, status::State, App}; +use crate::app::{status::State, App, command_interface::Command}; use self::handlers::{command, main, matrix, setup}; diff --git a/src/app/mod.rs b/src/app/mod.rs index 785fa8a..3b21e9d 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,9 +1,8 @@ -pub mod command; pub mod command_interface; pub mod events; pub mod status; -use std::path::Path; +use std::{path::Path, sync::mpsc::Sender as StdSender}; use accounts::{Account, AccountsManager}; use anyhow::{Context, Error, Result}; @@ -33,11 +32,11 @@ pub struct App<'ui> { impl App<'_> { pub fn new() -> Result { - fn set_up_lua() -> Lua { + fn set_up_lua(tx: StdSender) -> Lua { let lua = Lua::new(); lua.context(|mut lua_context| { - generate_ci_functions(&mut lua_context); + generate_ci_functions(&mut lua_context, tx); }); lua } @@ -62,7 +61,7 @@ impl App<'_> { input_listener_killer: CancellationToken::new(), matrix_listener_killer: CancellationToken::new(), - lua: set_up_lua(), + lua: set_up_lua(channel_tx), }) } -- 2.40.1 From 49818e0bfe34b142d5488fe4c53f549121e9189c Mon Sep 17 00:00:00 2001 From: Soispha Date: Sun, 16 Jul 2023 14:18:17 +0200 Subject: [PATCH 04/18] Refactor(app): Add a transmitter --- .../events/event_types/event/handlers/main.rs | 32 ++++++++---- src/app/mod.rs | 50 +++++++++---------- src/app/transmitter.rs | 43 ++++++++++++++++ 3 files changed, 89 insertions(+), 36 deletions(-) create mode 100644 src/app/transmitter.rs diff --git a/src/app/events/event_types/event/handlers/main.rs b/src/app/events/event_types/event/handlers/main.rs index 880339a..01d3097 100644 --- a/src/app/events/event_types/event/handlers/main.rs +++ b/src/app/events/event_types/event/handlers/main.rs @@ -2,7 +2,11 @@ use anyhow::Result; use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers}; use crate::{ - app::{command, command::Command, events::event_types::EventStatus, App}, + app::{ + command_interface::Command, + events::event_types::{Event, EventStatus}, + App, + }, ui::central, }; @@ -11,25 +15,33 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { - command::execute(app.channel_tx(), Command::Exit).await?; + app.transmitter + .send(Event::CommandEvent(Command::Exit)) + .await?; } CrosstermEvent::Key(KeyEvent { code: KeyCode::Tab, .. }) => { - command::execute(app.channel_tx(), Command::CyclePlanes).await?; + app.transmitter + .send(Event::CommandEvent(Command::CyclePlanes)) + .await?; } CrosstermEvent::Key(KeyEvent { code: KeyCode::BackTab, .. }) => { - command::execute(app.channel_tx(), Command::CyclePlanesRev).await?; + app.transmitter + .send(Event::CommandEvent(Command::CyclePlanesRev)) + .await?; } CrosstermEvent::Key(KeyEvent { code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, .. }) => { - command::execute(app.channel_tx(), Command::CommandLineShow).await?; + app.transmitter + .send(Event::CommandEvent(Command::CommandLineShow)) + .await?; } input => match app.ui.input_position() { central::InputPosition::MessageCompose => { @@ -39,11 +51,11 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { - command::execute( - app.channel_tx(), - Command::RoomMessageSend(app.ui.message_compose.lines().join("\n")), - ) - .await?; + app.transmitter + .send(Event::CommandEvent(Command::RoomMessageSend( + app.ui.message_compose.lines().join("\n"), + ))) + .await?; app.ui.message_compose_clear(); } _ => { diff --git a/src/app/mod.rs b/src/app/mod.rs index 3b21e9d..d4fc374 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,29 +1,31 @@ pub mod command_interface; pub mod events; pub mod status; +pub mod transmitter; use std::{path::Path, sync::mpsc::Sender as StdSender}; -use accounts::{Account, AccountsManager}; use anyhow::{Context, Error, Result}; use cli_log::info; use matrix_sdk::Client; use rlua::Lua; use status::{State, Status}; -use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; -use crate::{accounts, app::command_interface::generate_ci_functions, ui::{central, setup}}; +use crate::{ + accounts::{Account, AccountsManager}, + app::{command_interface::generate_ci_functions, events::event_types::Event}, + ui::{central, setup}, +}; -use self::events::event_types::{self, Event}; +use self::{events::event_types, transmitter::Transmitter}; pub struct App<'ui> { ui: central::UI<'ui>, - accounts_manager: accounts::AccountsManager, + accounts_manager: AccountsManager, status: Status, - channel_tx: mpsc::Sender, - channel_rx: mpsc::Receiver, + transmitter: Transmitter, input_listener_killer: CancellationToken, matrix_listener_killer: CancellationToken, @@ -49,19 +51,17 @@ impl App<'_> { None }; - let (channel_tx, channel_rx) = mpsc::channel(256); - + let transmitter = Transmitter::new(); Ok(Self { ui: central::UI::new()?, accounts_manager: AccountsManager::new(config)?, status: Status::new(None), - channel_tx, - channel_rx, + transmitter, input_listener_killer: CancellationToken::new(), matrix_listener_killer: CancellationToken::new(), - lua: set_up_lua(channel_tx), + lua: set_up_lua(transmitter.std_tx()), }) } @@ -83,7 +83,7 @@ impl App<'_> { pub async fn run(&mut self) -> Result<()> { // Spawn input event listener tokio::task::spawn(events::poll_input_events( - self.channel_tx.clone(), + self.transmitter.tx(), self.input_listener_killer.clone(), )); @@ -99,10 +99,11 @@ impl App<'_> { self.status.set_state(State::Main); self.ui.update(&self.status).await?; - let event: event_types::Event = match self.channel_rx.recv().await { - Some(e) => e, - None => return Err(Error::msg("Event channel has no senders")), - }; + let event = self + .transmitter + .recv() + .await + .context("Failed to get next event")?; match event.handle(self).await? { event_types::EventStatus::Ok => (), @@ -122,10 +123,11 @@ impl App<'_> { self.status.set_state(State::Setup); self.ui.update_setup().await?; - let event: event_types::Event = match self.channel_rx.recv().await { - Some(e) => e, - None => return Err(Error::msg("Event channel has no senders")), - }; + let event = self + .transmitter + .recv() + .await + .context("Failed to get next event")?; match event.handle(self).await? { event_types::EventStatus::Ok => (), @@ -149,7 +151,7 @@ impl App<'_> { // Spawn Matrix Event Listener tokio::task::spawn(events::poll_matrix_events( - self.channel_tx.clone(), + self.transmitter.tx(), self.matrix_listener_killer.clone(), client.clone(), )); @@ -203,8 +205,4 @@ impl App<'_> { pub fn client(&self) -> Option<&Client> { self.accounts_manager.client() } - - pub fn channel_tx(&self) -> &mpsc::Sender { - &self.channel_tx - } } diff --git a/src/app/transmitter.rs b/src/app/transmitter.rs new file mode 100644 index 0000000..c887459 --- /dev/null +++ b/src/app/transmitter.rs @@ -0,0 +1,43 @@ +use std::sync::mpsc as StdMpsc; + +use anyhow::{bail, Context, Result}; +use tokio::sync::mpsc; + +use super::events::event_types::Event; + +pub struct Transmitter { + tx: mpsc::Sender, + rx: mpsc::Receiver, + std_tx: StdMpsc::Sender, + std_rx: StdMpsc::Receiver, +} + +impl Transmitter { + pub fn new() -> Transmitter { + let (std_tx, std_rx) = StdMpsc::channel(); + let (tx, rx) = mpsc::channel(256); + Transmitter { + tx, + rx, + std_tx, + std_rx, + } + } + pub fn tx(&self) -> mpsc::Sender { + self.tx.to_owned() + } + + pub fn std_tx(&self) -> StdMpsc::Sender { + self.std_tx.to_owned() + } + + pub async fn recv(&mut self) -> Result { + 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") + } +} -- 2.40.1 From 866ec7c277d11b41743001b2e86a900fa2b6f7ab Mon Sep 17 00:00:00 2001 From: Soispha Date: Mon, 17 Jul 2023 00:17:53 +0200 Subject: [PATCH 05/18] Refactor(ci_command_handling): Move to the event handlers --- .../event_types/event/handlers/ci_output.rs | 11 ++++++++++ .../event_types/event/handlers/lua_command.rs | 22 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/app/events/event_types/event/handlers/ci_output.rs create mode 100644 src/app/events/event_types/event/handlers/lua_command.rs diff --git a/src/app/events/event_types/event/handlers/ci_output.rs b/src/app/events/event_types/event/handlers/ci_output.rs new file mode 100644 index 0000000..9df9da4 --- /dev/null +++ b/src/app/events/event_types/event/handlers/ci_output.rs @@ -0,0 +1,11 @@ +use anyhow::Result; +use cli_log::info; + +use crate::app::{events::event_types::EventStatus, App}; + +pub async fn handle(app: &mut App<'_>, output: &String) -> Result { + info!("Recieved command output: `{}`", output); + app.ui.set_command_output(output); + + Ok(EventStatus::Ok) +} diff --git a/src/app/events/event_types/event/handlers/lua_command.rs b/src/app/events/event_types/event/handlers/lua_command.rs new file mode 100644 index 0000000..ac459e6 --- /dev/null +++ b/src/app/events/event_types/event/handlers/lua_command.rs @@ -0,0 +1,22 @@ +use anyhow::{Context, Result}; +use cli_log::info; + +use crate::app::{App, events::event_types::{EventStatus, Event}}; + +pub async fn handle(app: &mut App<'_>, command: &str) -> Result { + info!("Recieved ci command: `{command}`; executing.."); + + // TODO: Should the ci support more than strings? + let output = app.lua.context(|context| -> Result { + let output = context + .load(&command) + .eval::() + .with_context(|| format!("Failed to execute: `{command}`"))?; + info!("Function evaluated to: `{output}`"); + Ok(output) + })?; + + app.transmitter.send(Event::CiOutput(output)); + + Ok(EventStatus::Ok) +} -- 2.40.1 From fc880d47d21df821becae5127b082a8c9271fe42 Mon Sep 17 00:00:00 2001 From: Soispha Date: Mon, 17 Jul 2023 06:08:32 +0200 Subject: [PATCH 06/18] Build(Lock_files): Update --- Cargo.lock | 198 ++++++++++++++++++++++++++++------------------------- flake.lock | 18 ++--- 2 files changed, 113 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c4c9f4..0664b60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,9 +75,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "anymap2" @@ -138,9 +138,9 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", ] [[package]] @@ -149,9 +149,9 @@ version = "0.1.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", ] [[package]] @@ -415,6 +415,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -547,8 +556,8 @@ checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.64", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.31", "strsim", "syn 1.0.109", ] @@ -560,18 +569,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core", - "quote 1.0.29", + "quote 1.0.31", "syn 1.0.109", ] [[package]] name = "dashmap" -version = "5.4.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" dependencies = [ "cfg-if", - "hashbrown 0.12.3", + "hashbrown 0.14.0", "lock_api", "once_cell", "parking_lot_core 0.9.8", @@ -602,8 +611,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" dependencies = [ "darling", - "proc-macro2 1.0.64", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.31", "syn 1.0.109", ] @@ -649,9 +658,9 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", ] [[package]] @@ -696,9 +705,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" @@ -848,9 +857,9 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", ] [[package]] @@ -1213,9 +1222,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" @@ -1291,9 +1300,10 @@ dependencies = [ name = "lua_macros" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "convert_case", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", ] [[package]] @@ -1602,9 +1612,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", ] [[package]] @@ -1717,9 +1727,9 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", ] [[package]] @@ -1788,9 +1798,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.64" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -1822,8 +1832,8 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools", - "proc-macro2 1.0.64", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.31", "syn 1.0.109", ] @@ -1838,11 +1848,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.29" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" dependencies = [ - "proc-macro2 1.0.64", + "proc-macro2 1.0.66", ] [[package]] @@ -2063,9 +2073,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" dependencies = [ "aho-corasick", "memchr", @@ -2074,9 +2084,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "reqwest" @@ -2230,8 +2240,8 @@ checksum = "0f82e91eb61cd86d9287303133ee55b54618eccb75a522cc22a42c15f5bda340" dependencies = [ "once_cell", "proc-macro-crate", - "proc-macro2 1.0.64", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.31", "ruma-identifiers-validation", "serde", "syn 1.0.109", @@ -2260,9 +2270,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "schannel" @@ -2304,38 +2314,38 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.169" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd51c3db8f9500d531e6c12dd0fd4ad13d133e9117f5aebac3cdbb8b6d9824b0" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a16be4fe5320ade08736447e3198294a5ea9a6d44dde6f35f0a5e06859c427a" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.169" +version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27738cfea0d944ab72c3ed01f3d5f23ec4322af8a1431e40ce630e4c01ea74fd" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", ] [[package]] name = "serde_json" -version = "1.0.100" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" +checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" dependencies = [ "itoa", "ryu", @@ -2380,9 +2390,9 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" +checksum = "b824b6e687aff278cdbf3b36f07aa52d4bd4099699324d5da86a2ebce3aa00b3" dependencies = [ "libc", "signal-hook-registry", @@ -2493,19 +2503,19 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.31", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.25" +version = "2.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", + "proc-macro2 1.0.66", + "quote 1.0.31", "unicode-ident", ] @@ -2538,9 +2548,9 @@ version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", ] [[package]] @@ -2593,9 +2603,9 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", ] [[package]] @@ -2639,9 +2649,9 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.19.12" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ "indexmap 2.0.0", "toml_datetime", @@ -2672,9 +2682,9 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", ] [[package]] @@ -2747,9 +2757,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-normalization" @@ -2912,9 +2922,9 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", "wasm-bindgen-shared", ] @@ -2936,7 +2946,7 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ - "quote 1.0.29", + "quote 1.0.31", "wasm-bindgen-macro-support", ] @@ -2946,9 +2956,9 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3089,9 +3099,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.9" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" +checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" dependencies = [ "memchr", ] @@ -3132,7 +3142,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.64", - "quote 1.0.29", - "syn 2.0.25", + "proc-macro2 1.0.66", + "quote 1.0.31", + "syn 2.0.26", ] diff --git a/flake.lock b/flake.lock index f639470..f952431 100644 --- a/flake.lock +++ b/flake.lock @@ -50,11 +50,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1687709756, - "narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=", + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", "owner": "numtide", "repo": "flake-utils", - "rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", "type": "github" }, "original": { @@ -65,11 +65,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1688829822, - "narHash": "sha256-hv56yK1fPHPt7SU2DboxBtdSbIuv9nym7Dss7Cn2jic=", + "lastModified": 1689449371, + "narHash": "sha256-sK3Oi8uEFrFPL83wKPV6w0+96NrmwqIpw9YFffMifVg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ed6afb10dfdfc97b6bcf0703f1bad8118e9e961b", + "rev": "29bcead8405cfe4c00085843eb372cc43837bb9d", "type": "github" }, "original": { @@ -98,11 +98,11 @@ ] }, "locked": { - "lastModified": 1688870171, - "narHash": "sha256-8tD8fheWPa7TaJoxzcU3iHkCrQQpOpdMN+HYqgZ1N5A=", + "lastModified": 1689561325, + "narHash": "sha256-+UABrHUXtWJSc9mM7oEKPIYQEhTzUVVNy2IPG9Lfrj0=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "5a932f10ac4bd59047d6e8b5780750ec76ea988a", + "rev": "d8a38aea13c67dc2ce10cff93eb274dcf455753f", "type": "github" }, "original": { -- 2.40.1 From 91ea3f65ea76f16a6a1ce46a30a28689e16f892e Mon Sep 17 00:00:00 2001 From: Soispha Date: Mon, 17 Jul 2023 07:23:36 +0200 Subject: [PATCH 07/18] Refactor(lua_macros): Remove dead code --- lua_macros/src/generate_noop_lua_function.rs | 11 ----------- lua_macros/src/lib.rs | 9 ++------- lua_macros/src/mark_as_ci_command.rs | 16 +--------------- 3 files changed, 3 insertions(+), 33 deletions(-) delete mode 100644 lua_macros/src/generate_noop_lua_function.rs diff --git a/lua_macros/src/generate_noop_lua_function.rs b/lua_macros/src/generate_noop_lua_function.rs deleted file mode 100644 index eac9e0a..0000000 --- a/lua_macros/src/generate_noop_lua_function.rs +++ /dev/null @@ -1,11 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::ToTokens; - -// TODO: Do we need this noop? -pub fn generate_default_lua_function(input: &syn::Field) -> TokenStream2 { - let output: TokenStream2 = syn::parse(input.into_token_stream().into()) - .expect("This is generated from valid rust code, it should stay that way."); - - output -} - diff --git a/lua_macros/src/lib.rs b/lua_macros/src/lib.rs index 12fe422..7e4b612 100644 --- a/lua_macros/src/lib.rs +++ b/lua_macros/src/lib.rs @@ -1,8 +1,6 @@ -mod generate_noop_lua_function; mod mark_as_ci_command; mod struct_to_ci_enum; -use generate_noop_lua_function::generate_default_lua_function; use mark_as_ci_command::generate_final_function; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; @@ -30,21 +28,18 @@ pub fn turn_struct_to_ci_commands(_attrs: TokenStream, input: TokenStream) -> To } /// Generate a default lua function implementation. +// TODO: Is this needed? #[proc_macro_attribute] pub fn gen_lua_function(_attrs: TokenStream, input: TokenStream) -> TokenStream { // Construct a representation of Rust code as a syntax tree // that we can manipulate - // let parser = Field::parse_named; let input = parser.parse(input) .expect("This is only defined for named fileds."); - // Build the trait implementation - let default_lua_function: TokenStream2 = generate_default_lua_function(&input); - quote! { - #default_lua_function + #input } .into() } diff --git a/lua_macros/src/mark_as_ci_command.rs b/lua_macros/src/mark_as_ci_command.rs index 08b715a..3a865db 100644 --- a/lua_macros/src/mark_as_ci_command.rs +++ b/lua_macros/src/mark_as_ci_command.rs @@ -26,21 +26,7 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn { let tx_send = match &input.sig.output { syn::ReturnType::Default => { - todo!( - "Does this case even trigger? All functions should have a output of (Result<$type, rlua::Error>)" - ); - quote! { - { - let tx: std::sync::mpsc::Sender = - context - .named_registry_value("sender_for_ci_commands") - .expect("This exists, it was set before"); - - tx - .send(Event::CommandEvent(Command::#function_name_pascal)) - .expect("This should work, as the reciever is not dropped"); - } - } + unreachable!("All functions should have a output of (Result<$type, rlua::Error>)"); } syn::ReturnType::Type(_, ret_type) => { let return_type = match *(ret_type.clone()) { -- 2.40.1 From c3a2b2d56658dfef8829c0d34eee7de387a3d4b9 Mon Sep 17 00:00:00 2001 From: Soispha Date: Mon, 17 Jul 2023 07:27:51 +0200 Subject: [PATCH 08/18] Fix(lua): Switch to mlua library, as it's better than rlua --- Cargo.lock | 49 +++++++++---------- Cargo.toml | 2 +- flake.nix | 1 + lua_macros/src/mark_as_ci_command.rs | 37 +++++++------- lua_macros/src/struct_to_ci_enum.rs | 14 +++--- src/app/command_interface.rs | 7 +-- .../event_types/event/handlers/lua_command.rs | 20 ++++---- src/app/mod.rs | 13 +++-- src/app/transmitter.rs | 11 ----- 9 files changed, 74 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0664b60..c5ca408 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1533,6 +1533,23 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "mlua" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07366ed2cd22a3b000aed076e2b68896fb46f06f1f5786c5962da73c0af01577" +dependencies = [ + "bstr", + "cc", + "futures-core", + "futures-task", + "futures-util", + "num-traits", + "once_cell", + "pkg-config", + "rustc-hash", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -2125,30 +2142,6 @@ dependencies = [ "winreg", ] -[[package]] -name = "rlua" -version = "0.19.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d33e5ba15c3d43178f283ed5863d4531e292fc0e56fb773f3bea45f18e3a42a" -dependencies = [ - "bitflags", - "bstr", - "libc", - "num-traits", - "rlua-lua54-sys", -] - -[[package]] -name = "rlua-lua54-sys" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aafabafe1895cb4a2be81a56d7ff3d46bf4b5d2f9cfdbea2ed404cdabe96474" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "ruma" version = "0.7.4" @@ -2254,6 +2247,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.37.23" @@ -2706,7 +2705,7 @@ dependencies = [ "indexmap 2.0.0", "lua_macros", "matrix-sdk", - "rlua", + "mlua", "serde", "tokio", "tokio-util", diff --git a/Cargo.toml b/Cargo.toml index 55e4b6d..a714f8c 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" -rlua = "0.19.7" +mlua = { version = "0.8.9", features = ["lua54", "async"] } diff --git a/flake.nix b/flake.nix index 72bee03..e3a5d47 100644 --- a/flake.nix +++ b/flake.nix @@ -55,6 +55,7 @@ ]; buildInputs = with pkgs; [ openssl + lua54Packages.stdlib ]; craneBuild = craneLib.buildPackage { diff --git a/lua_macros/src/mark_as_ci_command.rs b/lua_macros/src/mark_as_ci_command.rs index 3a865db..f8185dc 100644 --- a/lua_macros/src/mark_as_ci_command.rs +++ b/lua_macros/src/mark_as_ci_command.rs @@ -97,35 +97,38 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn { } _ => unimplemented!("Only for path types"), }; - match return_type { + let send_data = match return_type { ReturnType::Default => { quote! { { - let tx: std::sync::mpsc::Sender = - context - .named_registry_value("sender_for_ci_commands") - .expect("This exists, it was set before"); - - tx - .send(Event::CommandEvent(Command::#function_name_pascal)) - .expect("This should work, as the reciever is not dropped"); + Event::CommandEvent(Command::#function_name_pascal) } } } ReturnType::Type(_, _) => { quote! { { - let tx: std::sync::mpsc::Sender = - context - .named_registry_value("sender_for_ci_commands") - .expect("This exists, it was set before"); - - tx - .send(Event::CommandEvent(Command::#function_name_pascal(input_str))) - .expect("This should work, as the reciever is not dropped"); + Event::CommandEvent(Command::#function_name_pascal(input_str)) } } } + }; + quote! { + { + let tx: std::sync::Arc>> = + lua + .named_registry_value("sender_for_ci_commands") + .expect("This exists, it was set before"); + + 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) + .await + .expect("This should work, as the reciever is not dropped"); + } } } }; diff --git a/lua_macros/src/struct_to_ci_enum.rs b/lua_macros/src/struct_to_ci_enum.rs index f231249..76b3fd1 100644 --- a/lua_macros/src/struct_to_ci_enum.rs +++ b/lua_macros/src/struct_to_ci_enum.rs @@ -22,10 +22,10 @@ pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 { .expect("These are only the named field, thus they all should have a name."); functions_to_generate.push(quote! { #[ci_command] - fn #function_name(context: Context, input_str: String) -> Result<(), rlua::Error> { + async fn #function_name(lua: &mlua::Lua, input_str: String) -> Result<(), mlua::Error> { Ok(()) } - }); + }); generate_ci_part(field) } else { generate_ci_part(field) @@ -41,11 +41,11 @@ pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 { let functions_to_generate: TokenStream2 = functions_to_generate.into_iter().collect(); let gen = quote! { pub fn generate_ci_functions( - context: &mut rlua::Context, - tx: std::sync::mpsc::Sender) + lua: &mut mlua::Lua, + tx: tokio::sync::mpsc::Sender) { - context.set_named_registry_value("sender_for_ci_commands", tx).expect("This should always work, as the value is added before all else"); - let globals = context.globals(); + 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"); + let globals = lua.globals(); #input_tokens } #functions_to_generate @@ -61,7 +61,7 @@ fn generate_ci_part(field: &syn::Field) -> TokenStream2 { let function_name_ident = format_ident!("fun_{}", field_ident); let function_name = format!("{}", field_ident); quote! { - let #function_name_ident = context.create_function(#field_ident).expect( + let #function_name_ident = lua.create_async_function(#field_ident).expect( &format!( "The function: `{}` should be defined", #function_name diff --git a/src/app/command_interface.rs b/src/app/command_interface.rs index 87f184d..6f09452 100644 --- a/src/app/command_interface.rs +++ b/src/app/command_interface.rs @@ -1,4 +1,5 @@ // 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 rlua::Context; @@ -21,7 +22,7 @@ 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] -struct Commands<'lua> { +struct Commands { greet: fn(usize) -> String, // Closes the application @@ -42,11 +43,11 @@ struct Commands<'lua> { } #[ci_command] -fn greet(context: Context, input_str: String) -> Result { +async fn greet(lua: &mlua::Lua, input_str: String) -> Result { Ok(format!("Name is {}", input_str)) } #[ci_command] -fn room_message_send(context: Context, input_str: String) -> Result { +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/lua_command.rs b/src/app/events/event_types/event/handlers/lua_command.rs index ac459e6..5994a4f 100644 --- a/src/app/events/event_types/event/handlers/lua_command.rs +++ b/src/app/events/event_types/event/handlers/lua_command.rs @@ -1,20 +1,22 @@ use anyhow::{Context, Result}; use cli_log::info; -use crate::app::{App, events::event_types::{EventStatus, Event}}; +use crate::app::{ + events::event_types::{Event, EventStatus}, + App, +}; pub async fn handle(app: &mut App<'_>, command: &str) -> Result { info!("Recieved ci command: `{command}`; executing.."); // TODO: Should the ci support more than strings? - let output = app.lua.context(|context| -> Result { - let output = context - .load(&command) - .eval::() - .with_context(|| format!("Failed to execute: `{command}`"))?; - info!("Function evaluated to: `{output}`"); - Ok(output) - })?; + let output = app + .lua + .load(command) + .eval_async::() + .await + .with_context(|| format!("Failed to execute: `{command}`"))?; + info!("Function evaluated to: `{output}`"); app.transmitter.send(Event::CiOutput(output)); diff --git a/src/app/mod.rs b/src/app/mod.rs index d4fc374..058f8ef 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3,13 +3,14 @@ pub mod events; pub mod status; pub mod transmitter; -use std::{path::Path, sync::mpsc::Sender as StdSender}; +use std::path::Path; use anyhow::{Context, Error, Result}; use cli_log::info; use matrix_sdk::Client; -use rlua::Lua; +use mlua::Lua; use status::{State, Status}; +use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; use crate::{ @@ -34,12 +35,10 @@ pub struct App<'ui> { impl App<'_> { pub fn new() -> Result { - fn set_up_lua(tx: StdSender) -> Lua { + fn set_up_lua(tx: mpsc::Sender) -> Lua { let lua = Lua::new(); - lua.context(|mut lua_context| { - generate_ci_functions(&mut lua_context, tx); - }); + generate_ci_functions(&mut lua, tx); lua } @@ -61,7 +60,7 @@ impl App<'_> { input_listener_killer: CancellationToken::new(), matrix_listener_killer: CancellationToken::new(), - lua: set_up_lua(transmitter.std_tx()), + lua: set_up_lua(transmitter.tx()), }) } diff --git a/src/app/transmitter.rs b/src/app/transmitter.rs index c887459..aaa96b6 100644 --- a/src/app/transmitter.rs +++ b/src/app/transmitter.rs @@ -1,5 +1,3 @@ -use std::sync::mpsc as StdMpsc; - use anyhow::{bail, Context, Result}; use tokio::sync::mpsc; @@ -8,29 +6,20 @@ use super::events::event_types::Event; pub struct Transmitter { tx: mpsc::Sender, rx: mpsc::Receiver, - std_tx: StdMpsc::Sender, - std_rx: StdMpsc::Receiver, } impl Transmitter { pub fn new() -> Transmitter { - let (std_tx, std_rx) = StdMpsc::channel(); let (tx, rx) = mpsc::channel(256); Transmitter { tx, rx, - std_tx, - std_rx, } } pub fn tx(&self) -> mpsc::Sender { self.tx.to_owned() } - pub fn std_tx(&self) -> StdMpsc::Sender { - self.std_tx.to_owned() - } - pub async fn recv(&mut self) -> Result { match self.rx.recv().await { Some(event) => Ok(event), -- 2.40.1 From 641265068697e8170ea684a1fb8ac99db6c2d7dc Mon Sep 17 00:00:00 2001 From: Soispha Date: Tue, 18 Jul 2023 08:03:16 +0200 Subject: [PATCH 09/18] Fix(lua_macros): Add sender trough app_data --- lua_macros/src/mark_as_ci_command.rs | 17 ++++++++--------- lua_macros/src/struct_to_ci_enum.rs | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lua_macros/src/mark_as_ci_command.rs b/lua_macros/src/mark_as_ci_command.rs index f8185dc..69d7511 100644 --- a/lua_macros/src/mark_as_ci_command.rs +++ b/lua_macros/src/mark_as_ci_command.rs @@ -88,7 +88,7 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn { .expect("This is valid."); return_type_as_return_type } else { - // There is only rlua + // There is only mlua::Error left ReturnType::Default } } @@ -108,23 +108,22 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn { ReturnType::Type(_, _) => { quote! { { - Event::CommandEvent(Command::#function_name_pascal(input_str)) + Event::CommandEvent(Command::#function_name_pascal(input_str.clone())) } } } }; quote! { { - let tx: std::sync::Arc>> = + let tx: + core::cell::Ref< + tokio::sync::mpsc::Sender + > = lua - .named_registry_value("sender_for_ci_commands") + .app_data_ref() .expect("This exists, it was set before"); - 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"); + (*tx) .send(#send_data) .await .expect("This should work, as the reciever is not dropped"); diff --git a/lua_macros/src/struct_to_ci_enum.rs b/lua_macros/src/struct_to_ci_enum.rs index 76b3fd1..7f7ee65 100644 --- a/lua_macros/src/struct_to_ci_enum.rs +++ b/lua_macros/src/struct_to_ci_enum.rs @@ -44,7 +44,7 @@ pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 { lua: &mut mlua::Lua, tx: tokio::sync::mpsc::Sender) { - 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(); #input_tokens } -- 2.40.1 From a413171ffe861dc7ee049a0202ec846d3181c623 Mon Sep 17 00:00:00 2001 From: Soispha Date: Tue, 18 Jul 2023 08:07:21 +0200 Subject: [PATCH 10/18] Refactor(transmitter): Go back to plain tx,rx channels --- .../event_types/event/handlers/lua_command.rs | 2 +- .../events/event_types/event/handlers/main.rs | 12 +++---- src/app/mod.rs | 34 ++++++++----------- src/app/transmitter.rs | 32 ----------------- 4 files changed, 21 insertions(+), 59 deletions(-) delete mode 100644 src/app/transmitter.rs 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 5994a4f..e6b60ea 100644 --- a/src/app/events/event_types/event/handlers/lua_command.rs +++ b/src/app/events/event_types/event/handlers/lua_command.rs @@ -18,7 +18,7 @@ pub async fn handle(app: &mut App<'_>, command: &str) -> Result { .with_context(|| format!("Failed to execute: `{command}`"))?; 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) } diff --git a/src/app/events/event_types/event/handlers/main.rs b/src/app/events/event_types/event/handlers/main.rs index 01d3097..8e65604 100644 --- a/src/app/events/event_types/event/handlers/main.rs +++ b/src/app/events/event_types/event/handlers/main.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers}; use crate::{ @@ -15,14 +15,14 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { - app.transmitter + app.tx .send(Event::CommandEvent(Command::Exit)) .await?; } CrosstermEvent::Key(KeyEvent { code: KeyCode::Tab, .. }) => { - app.transmitter + app.tx .send(Event::CommandEvent(Command::CyclePlanes)) .await?; } @@ -30,7 +30,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { - app.transmitter + app.tx .send(Event::CommandEvent(Command::CyclePlanesRev)) .await?; } @@ -39,7 +39,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { - app.transmitter + app.tx .send(Event::CommandEvent(Command::CommandLineShow)) .await?; } @@ -51,7 +51,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { - app.transmitter + app.tx .send(Event::CommandEvent(Command::RoomMessageSend( app.ui.message_compose.lines().join("\n"), ))) diff --git a/src/app/mod.rs b/src/app/mod.rs index 058f8ef..8580990 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,11 +1,10 @@ pub mod command_interface; pub mod events; pub mod status; -pub mod transmitter; use std::path::Path; -use anyhow::{Context, Error, Result}; +use anyhow::{Error, Result, Context}; use cli_log::info; use matrix_sdk::Client; use mlua::Lua; @@ -19,14 +18,16 @@ use crate::{ ui::{central, setup}, }; -use self::{events::event_types, transmitter::Transmitter}; +use self::events::event_types; pub struct App<'ui> { ui: central::UI<'ui>, accounts_manager: AccountsManager, status: Status, - transmitter: Transmitter, + tx: mpsc::Sender, + rx: mpsc::Receiver, + input_listener_killer: CancellationToken, matrix_listener_killer: CancellationToken, @@ -36,7 +37,7 @@ pub struct App<'ui> { impl App<'_> { pub fn new() -> Result { fn set_up_lua(tx: mpsc::Sender) -> Lua { - let lua = Lua::new(); + let mut lua = Lua::new(); generate_ci_functions(&mut lua, tx); lua @@ -50,17 +51,18 @@ impl App<'_> { None }; - let transmitter = Transmitter::new(); + let (tx, rx) = mpsc::channel(256); Ok(Self { ui: central::UI::new()?, accounts_manager: AccountsManager::new(config)?, status: Status::new(None), - transmitter, + tx: tx.clone(), + rx, input_listener_killer: CancellationToken::new(), matrix_listener_killer: CancellationToken::new(), - lua: set_up_lua(transmitter.tx()), + lua: set_up_lua(tx), }) } @@ -82,7 +84,7 @@ impl App<'_> { pub async fn run(&mut self) -> Result<()> { // Spawn input event listener tokio::task::spawn(events::poll_input_events( - self.transmitter.tx(), + self.tx.clone(), self.input_listener_killer.clone(), )); @@ -98,11 +100,7 @@ impl App<'_> { self.status.set_state(State::Main); self.ui.update(&self.status).await?; - let event = self - .transmitter - .recv() - .await - .context("Failed to get next event")?; + let event = self.rx.recv().await.context("Failed to get next event")?; match event.handle(self).await? { event_types::EventStatus::Ok => (), @@ -122,11 +120,7 @@ impl App<'_> { self.status.set_state(State::Setup); self.ui.update_setup().await?; - let event = self - .transmitter - .recv() - .await - .context("Failed to get next event")?; + let event = self.rx.recv().await.context("Failed to get next event")?; match event.handle(self).await? { event_types::EventStatus::Ok => (), @@ -150,7 +144,7 @@ impl App<'_> { // Spawn Matrix Event Listener tokio::task::spawn(events::poll_matrix_events( - self.transmitter.tx(), + self.tx.clone(), self.matrix_listener_killer.clone(), client.clone(), )); diff --git a/src/app/transmitter.rs b/src/app/transmitter.rs deleted file mode 100644 index aaa96b6..0000000 --- a/src/app/transmitter.rs +++ /dev/null @@ -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, - rx: mpsc::Receiver, -} - -impl Transmitter { - pub fn new() -> Transmitter { - let (tx, rx) = mpsc::channel(256); - Transmitter { - tx, - rx, - } - } - pub fn tx(&self) -> mpsc::Sender { - self.tx.to_owned() - } - - pub async fn recv(&mut self) -> Result { - 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") - } -} -- 2.40.1 From 14333944dc3d58de2ef517d5dad7883454b293ba Mon Sep 17 00:00:00 2001 From: Soispha Date: Tue, 18 Jul 2023 08:11:21 +0200 Subject: [PATCH 11/18] Fix(handles): Add command handling over the internal event stream --- .../events/event_types/event/handlers/command.rs | 6 +++++- .../events/event_types/event/handlers/main.rs | 16 +++++----------- src/app/events/event_types/event/mod.rs | 14 ++++++++++++-- src/app/mod.rs | 15 --------------- src/ui/central/mod.rs | 14 ++++++++++++-- 5 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/app/events/event_types/event/handlers/command.rs b/src/app/events/event_types/event/handlers/command.rs index 364ad23..78a58ac 100644 --- a/src/app/events/event_types/event/handlers/command.rs +++ b/src/app/events/event_types/event/handlers/command.rs @@ -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 cli_log::info; @@ -32,5 +32,9 @@ pub async fn handle(app: &mut App<'_>, command: &Command) -> Result } EventStatus::Ok } + Command::Greet(name) => { + info!("Greated {}", name); + 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 8e65604..4e87c40 100644 --- a/src/app/events/event_types/event/handlers/main.rs +++ b/src/app/events/event_types/event/handlers/main.rs @@ -155,7 +155,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { - let cli_event = app.ui + let ci_event = app.ui .cli .as_mut() .expect("This is already checked") @@ -165,16 +165,10 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result { app.ui diff --git a/src/app/events/event_types/event/mod.rs b/src/app/events/event_types/event/mod.rs index b37d452..1e48bc6 100644 --- a/src/app/events/event_types/event/mod.rs +++ b/src/app/events/event_types/event/mod.rs @@ -3,9 +3,9 @@ mod handlers; use anyhow::{Context, Result}; 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; @@ -14,6 +14,8 @@ pub enum Event { InputEvent(CrosstermEvent), MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse), CommandEvent(Command), + CiOutput(String), + LuaCommand(String), } impl Event { @@ -26,6 +28,14 @@ impl 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::InputEvent(event) => match app.status.state() { State::None => Ok(EventStatus::Ok), diff --git a/src/app/mod.rs b/src/app/mod.rs index 8580990..e745aa3 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -66,21 +66,6 @@ impl App<'_> { }) } - pub async fn handle_ci_event(&self, event: &str) -> Result { - info!("Recieved ci event: `{event}`; executing.."); - - // TODO: Should the ci support more than strings? - let output = self.lua.context(|context| -> Result { - let output = context - .load(&event) - .eval::() - .with_context(|| format!("Failed to execute: `{event}`"))?; - info!("Function evaluated to: `{output}`"); - Ok(output) - })?; - Ok(output) - } - pub async fn run(&mut self) -> Result<()> { // Spawn input event listener tokio::task::spawn(events::poll_input_events( diff --git a/src/ui/central/mod.rs b/src/ui/central/mod.rs index e03e658..882584d 100644 --- a/src/ui/central/mod.rs +++ b/src/ui/central/mod.rs @@ -2,8 +2,8 @@ pub mod update; use std::io::Stdout; -use anyhow::{bail, Result, Context}; -use cli_log::info; +use anyhow::{bail, Context, Result}; +use cli_log::{info, warn}; use crossterm::{ event::DisableMouseCapture, 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) { self.input_position = InputPosition::CLI; if self.cli.is_some() { -- 2.40.1 From 734328787e13c50fbcee8eb15f19df8bd34d0ac6 Mon Sep 17 00:00:00 2001 From: Soispha Date: Tue, 18 Jul 2023 08:12:33 +0200 Subject: [PATCH 12/18] Fix(ui_widgets): Add missing lifetimes --- src/ui/central/update/widgets/messages.rs | 2 +- src/ui/central/update/widgets/room_info.rs | 2 +- src/ui/central/update/widgets/rooms.rs | 2 +- src/ui/central/update/widgets/status.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/central/update/widgets/messages.rs b/src/ui/central/update/widgets/messages.rs index 0c077b8..9a4f64d 100644 --- a/src/ui/central/update/widgets/messages.rs +++ b/src/ui/central/update/widgets/messages.rs @@ -9,7 +9,7 @@ use tui::{ use crate::{app::status::Room, ui::central::InputPosition}; -pub fn init<'a>(room: Option<&Room>, colors: &Vec) -> Result<(List<'a>, ListState)> { +pub fn init<'a>(room: Option<&'a Room>, colors: &Vec) -> Result<(List<'a>, ListState)> { let content = match room { Some(room) => get_content_from_room(room).context("Failed to get content from room")?, None => vec![ListItem::new(Text::styled( diff --git a/src/ui/central/update/widgets/room_info.rs b/src/ui/central/update/widgets/room_info.rs index a277e65..3563c55 100644 --- a/src/ui/central/update/widgets/room_info.rs +++ b/src/ui/central/update/widgets/room_info.rs @@ -6,7 +6,7 @@ use tui::{ use crate::{app::status::Room, ui::central::InputPosition}; -pub fn init<'a>(room: Option<&Room>, colors: &Vec) -> Paragraph<'a> { +pub fn init<'a>(room: Option<&'a Room>, colors: &Vec) -> Paragraph<'a> { let mut room_info_content = Text::default(); if let Some(room) = room { room_info_content.extend(Text::styled(room.name(), Style::default().fg(Color::Cyan))); diff --git a/src/ui/central/update/widgets/rooms.rs b/src/ui/central/update/widgets/rooms.rs index 4e09a31..f981e96 100644 --- a/src/ui/central/update/widgets/rooms.rs +++ b/src/ui/central/update/widgets/rooms.rs @@ -6,7 +6,7 @@ use tui::{ use crate::{app::status::Status, ui::central::InputPosition}; -pub fn init<'a>(status: &Status, colors: &Vec) -> List<'a> { +pub fn init<'a>(status: &'a Status, colors: &Vec) -> List<'a> { let rooms_content: Vec<_> = status .rooms() .iter() diff --git a/src/ui/central/update/widgets/status.rs b/src/ui/central/update/widgets/status.rs index 108668e..0d87034 100644 --- a/src/ui/central/update/widgets/status.rs +++ b/src/ui/central/update/widgets/status.rs @@ -7,7 +7,7 @@ use tui::{ use crate::{app::status::Status, ui::central::InputPosition}; -pub fn init<'a>(status: &Status, colors: &Vec) -> Paragraph<'a> { +pub fn init<'a>(status: &'a Status, colors: &Vec) -> Paragraph<'a> { let mut status_content = Text::styled( status.account_name(), Style::default().add_modifier(Modifier::BOLD), -- 2.40.1 From fbd1672d0375170fc8b794091dab041a09d22fa7 Mon Sep 17 00:00:00 2001 From: Soispha Date: Wed, 19 Jul 2023 06:28:48 +0200 Subject: [PATCH 13/18] Style(treewide): Reorder imports and modules --- src/app/command_interface.rs | 1 - src/app/events/event_types/event/handlers/mod.rs | 13 ++++++++++--- src/app/status.rs | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/app/command_interface.rs b/src/app/command_interface.rs index 6f09452..4ed0eba 100644 --- a/src/app/command_interface.rs +++ b/src/app/command_interface.rs @@ -1,7 +1,6 @@ // 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 rlua::Context; use super::events::event_types::Event; diff --git a/src/app/events/event_types/event/handlers/mod.rs b/src/app/events/event_types/event/handlers/mod.rs index d3fe26c..98b60ad 100644 --- a/src/app/events/event_types/event/handlers/mod.rs +++ b/src/app/events/event_types/event/handlers/mod.rs @@ -1,4 +1,11 @@ -pub mod command; -pub mod main; -pub mod matrix; +// input events pub mod setup; +pub mod main; + +// matrix +pub mod matrix; + +// ci +pub mod ci_output; +pub mod command; +pub mod lua_command; diff --git a/src/app/status.rs b/src/app/status.rs index 430f8a8..2a858f9 100644 --- a/src/app/status.rs +++ b/src/app/status.rs @@ -1,5 +1,5 @@ use anyhow::{Error, Result}; -use cli_log::{warn, info}; +use cli_log::warn; use indexmap::IndexMap; use matrix_sdk::{ room::MessagesOptions, -- 2.40.1 From 2a2c173683b4992942cb7dfdf40ca159fd1445c0 Mon Sep 17 00:00:00 2001 From: Soispha Date: Wed, 19 Jul 2023 06:32:19 +0200 Subject: [PATCH 14/18] Fix(command_interface): Use comments to generate the help function The generation part is yet to be done, but the comments itself are supposed to be the way of documentation. --- src/app/command_interface.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/command_interface.rs b/src/app/command_interface.rs index 4ed0eba..1f4b9bc 100644 --- a/src/app/command_interface.rs +++ b/src/app/command_interface.rs @@ -22,22 +22,30 @@ use super::events::event_types::Event; /// `String` and `()`). #[turn_struct_to_ci_commands] struct Commands { + /// Greets the user greet: fn(usize) -> String, - // Closes the application + /// Closes the application #[gen_default_lua_function] 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(), - //// 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, } -- 2.40.1 From 27e3ff228cb426da8a302557a4cd8cf01f39065e Mon Sep 17 00:00:00 2001 From: Soispha Date: Thu, 20 Jul 2023 21:39:38 +0200 Subject: [PATCH 15/18] Build(flake): Make it easier to switch to nightly --- flake.nix | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index e3a5d47..9ce72bf 100644 --- a/flake.nix +++ b/flake.nix @@ -45,10 +45,13 @@ overlays = [(import rust-overlay)]; }; - #rust-nightly = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default); - rust-stable = pkgs.rust-bin.stable.latest.default; + nightly = true; + rust = + if nightly + then pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default) + else pkgs.rust-bin.stable.latest.default; - craneLib = (crane.mkLib pkgs).overrideToolchain rust-stable; + craneLib = (crane.mkLib pkgs).overrideToolchain rust; nativeBuildInputs = with pkgs; [ pkg-config @@ -79,7 +82,7 @@ statix ltex-ls - rust-stable + rust rust-analyzer cargo-edit cargo-expand -- 2.40.1 From c243c90cabc1b68a747ed099c83795543020b43c Mon Sep 17 00:00:00 2001 From: Soispha Date: Thu, 20 Jul 2023 21:43:41 +0200 Subject: [PATCH 16/18] Fix(lua_macros): Rework to support new command focused layout The internal commands are wrapped by a lua api, which allows to write the whole thing a little bit more language agnostic. --- lua_macros/src/lib.rs | 84 ++++++----- lua_macros/src/mark_as_ci_command.rs | 56 +++++-- .../generate_command_enum.rs | 65 +++++++++ .../generate_generate_ci_function.rs} | 137 +++++++----------- .../generate_help_function.rs | 53 +++++++ lua_macros/src/struct_to_ci_enum/mod.rs | 7 + 6 files changed, 261 insertions(+), 141 deletions(-) create mode 100644 lua_macros/src/struct_to_ci_enum/generate_command_enum.rs rename lua_macros/src/{struct_to_ci_enum.rs => struct_to_ci_enum/generate_generate_ci_function.rs} (50%) create mode 100644 lua_macros/src/struct_to_ci_enum/generate_help_function.rs create mode 100644 lua_macros/src/struct_to_ci_enum/mod.rs diff --git a/lua_macros/src/lib.rs b/lua_macros/src/lib.rs index 7e4b612..871dd3f 100644 --- a/lua_macros/src/lib.rs +++ b/lua_macros/src/lib.rs @@ -3,61 +3,67 @@ mod struct_to_ci_enum; use mark_as_ci_command::generate_final_function; use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use struct_to_ci_enum::{generate_command_enum, generate_generate_ci_function}; -use syn::{self, ItemFn, Field, parse::Parser}; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{format_ident, quote}; +use struct_to_ci_enum::{generate_command_enum, generate_generate_ci_function, generate_help_function}; +use syn::{self, parse_quote, parse_str, DeriveInput, FieldMutability, ItemFn, Token, Visibility}; #[proc_macro_attribute] -pub fn turn_struct_to_ci_commands(_attrs: TokenStream, input: TokenStream) -> TokenStream { +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 input = syn::parse(input) - .expect("This should always be valid rust code, as it's extracted from direct code"); + 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(); + + let attr_parsed = parse_quote! { + /// This is a help function + }; + + named_fields.named.push(syn::Field { + attrs: vec![attr_parsed], + // attrs: attr_parser + // .parse("#[doc = r\"This is a help function\"]".to_token_stream().into()) + // .expect("See reason for other one"), + vis: Visibility::Inherited, + mutability: FieldMutability::None, + ident: Some(format_ident!("help")), + colon_token: Some(Token![:](Span::call_site())), + ty: parse_str("fn(Option) -> String").expect("This is static and valid rust code"), + }); + + match &mut input.data { + syn::Data::Struct(input) => input.fields = syn::Fields::Named(named_fields.clone()), + _ => unreachable!("This was a DataStruct before"), + }; // Build the trait implementation let generate_ci_function: TokenStream2 = generate_generate_ci_function(&input); - let command_enum = generate_command_enum(&input); + let command_enum = generate_command_enum(&named_fields); + + let help_function = generate_help_function(&named_fields); quote! { #command_enum + #generate_ci_function + + //#help_function } .into() } -/// Generate a default lua function implementation. -// TODO: Is this needed? -#[proc_macro_attribute] -pub fn gen_lua_function(_attrs: TokenStream, input: TokenStream) -> TokenStream { - // Construct a representation of Rust code as a syntax tree - // that we can manipulate - let parser = Field::parse_named; - let input = parser.parse(input) - .expect("This is only defined for named fileds."); - - - quote! { - #input - } - .into() -} - -/// Turn a function into a valid ci command function #[proc_macro_attribute] pub fn ci_command(_attrs: TokenStream, input: TokenStream) -> TokenStream { - // Construct a representation of Rust code as a syntax tree - // that we can manipulate - let mut input: ItemFn = syn::parse(input) - .expect("This should always be valid rust code, as it's extracted from direct code"); - - // Build the trait implementation - let output_function: TokenStream2 = generate_final_function(&mut input); - - //panic!("{:#?}", output_function); - quote! { - #output_function - } - .into() + let mut input: ItemFn = syn::parse(input).expect("This should always be valid rust code, as it's extracted from direct code"); + let output_function = generate_final_function(&mut input); + output_function.into() } diff --git a/lua_macros/src/mark_as_ci_command.rs b/lua_macros/src/mark_as_ci_command.rs index 69d7511..76e2fc1 100644 --- a/lua_macros/src/mark_as_ci_command.rs +++ b/lua_macros/src/mark_as_ci_command.rs @@ -58,18 +58,13 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn { // There should only be two segments (the type is ) if filtered_paths.len() > 2 { - unreachable!( - "There should be no more than two filtered_output, but got: {:#?}", - filtered_paths - ) + unreachable!("There should be no more than two filtered_output, but got: {:#?}", filtered_paths) } else if filtered_paths.len() <= 0 { - unreachable!( - "There should be more than zero filtered_output, but got: {:#?}", - filtered_paths - ) + unreachable!("There should be more than zero filtered_output, but got: {:#?}", filtered_paths) } + if filtered_paths.len() == 2 { - // There is something else than rlua + // There is something else than mlua::Error let gen_type = if let GenericArgument::Type(ret_type) = filtered_paths .first() @@ -97,36 +92,64 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn { } _ => unimplemented!("Only for path types"), }; + let send_data = match return_type { ReturnType::Default => { quote! { { - Event::CommandEvent(Command::#function_name_pascal) + Event::CommandEvent(Command::#function_name_pascal, None) } } } ReturnType::Type(_, _) => { quote! { { - Event::CommandEvent(Command::#function_name_pascal(input_str.clone())) + Event::CommandEvent(Command::#function_name_pascal(input.clone()), Some(callback_tx)) } } } }; + + let output_return = match return_type { + ReturnType::Default => { + quote! { + { + return Ok(()); + } + } + } + ReturnType::Type(_, _) => { + quote! { + { + if let Some(output) = callback_rx.recv().await { + callback_rx.close(); + return Ok(output); + } else { + return Err(mlua::Error::ExternalError(Arc::new(Error::new( + ErrorKind::Other, + "Callback reciever dropped", + )))); + } + } + } + } + }; + quote! { { + let (callback_tx, mut callback_rx) = tokio::sync::mpsc::channel::(256); + let tx: - core::cell::Ref< - tokio::sync::mpsc::Sender - > = + core::cell::Ref> = lua .app_data_ref() .expect("This exists, it was set before"); (*tx) - .send(#send_data) - .await + .try_send(#send_data) .expect("This should work, as the reciever is not dropped"); + + #output_return } } } @@ -134,6 +157,7 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn { let tx_send_block: Block = syn::parse(tx_send.into()).expect("This is a static string, it will always parse"); + let tx_send_expr_block = ExprBlock { attrs: vec![], label: None, diff --git a/lua_macros/src/struct_to_ci_enum/generate_command_enum.rs b/lua_macros/src/struct_to_ci_enum/generate_command_enum.rs new file mode 100644 index 0000000..8e5fd7e --- /dev/null +++ b/lua_macros/src/struct_to_ci_enum/generate_command_enum.rs @@ -0,0 +1,65 @@ +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::Type; + +pub fn generate_command_enum(input: &syn::FieldsNamed) -> TokenStream2 { + let input_tokens: TokenStream2 = input + .named + .iter() + .map(|field| -> TokenStream2 { + let field_ident = field + .ident + .as_ref() + .expect("These are only the named fields, thus they should all have a ident."); + + let enum_variant_type = match &field.ty { + syn::Type::BareFn(function) => { + let return_path = &function.inputs; + + let input_type: Option = if return_path.len() == 1 { + Some( + return_path + .last() + .expect("The last element exists") + .ty + .clone(), + ) + } else if return_path.len() == 0 { + None + } else { + panic!("The Function can only take on argument, or none"); + }; + input_type + } + _ => unimplemented!("This is only implemented for bare function types"), + }; + + let enum_variant_name = format_ident!( + "{}", + field_ident + .to_string() + .from_case(Case::Snake) + .to_case(Case::Pascal) + ); + if enum_variant_type.is_some() { + quote! { + #enum_variant_name (#enum_variant_type), + } + .into() + } else { + quote! { + #enum_variant_name, + } + } + }) + .collect(); + + let gen = quote! { + #[derive(Debug)] + pub enum Command { + #input_tokens + } + }; + gen.into() +} diff --git a/lua_macros/src/struct_to_ci_enum.rs b/lua_macros/src/struct_to_ci_enum/generate_generate_ci_function.rs similarity index 50% rename from lua_macros/src/struct_to_ci_enum.rs rename to lua_macros/src/struct_to_ci_enum/generate_generate_ci_function.rs index 7f7ee65..3c690ec 100644 --- a/lua_macros/src/struct_to_ci_enum.rs +++ b/lua_macros/src/struct_to_ci_enum/generate_generate_ci_function.rs @@ -1,63 +1,13 @@ -use convert_case::{Case, Casing}; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; -use syn::{self, ReturnType}; +use syn::{parse_quote, ReturnType, Type}; -pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 { - let mut functions_to_generate: Vec = vec![]; - let input_tokens: TokenStream2 = match &input.data { - syn::Data::Struct(input) => match &input.fields { - syn::Fields::Named(named_fields) => named_fields - .named - .iter() - .map(|field| -> TokenStream2 { - if field.attrs.iter().any(|attribute| { - attribute.path() - == &syn::parse_str::("gen_default_lua_function") - .expect("This is valid rust code") - }) { - let function_name = field - .ident - .as_ref() - .expect("These are only the named field, thus they all should have a name."); - functions_to_generate.push(quote! { - #[ci_command] - async fn #function_name(lua: &mlua::Lua, input_str: String) -> Result<(), mlua::Error> { - Ok(()) - } - }); - generate_ci_part(field) - } else { - generate_ci_part(field) - } - }) - .collect(), - - _ => unimplemented!("Only implemented for named fileds"), - }, - _ => unimplemented!("Only implemented for structs"), - }; - - let functions_to_generate: TokenStream2 = functions_to_generate.into_iter().collect(); - let gen = quote! { - pub fn generate_ci_functions( - lua: &mut mlua::Lua, - tx: tokio::sync::mpsc::Sender) - { - lua.set_app_data(tx); - let globals = lua.globals(); - #input_tokens - } - #functions_to_generate - }; - gen.into() -} - -fn generate_ci_part(field: &syn::Field) -> TokenStream2 { +fn generate_ci_function_exposure(field: &syn::Field) -> TokenStream2 { let field_ident = field .ident .as_ref() .expect("These are only the named field, thus they all should have a name."); + let function_name_ident = format_ident!("fun_{}", field_ident); let function_name = format!("{}", field_ident); quote! { @@ -79,62 +29,77 @@ fn generate_ci_part(field: &syn::Field) -> TokenStream2 { .into() } -pub fn generate_command_enum(input: &syn::DeriveInput) -> TokenStream2 { - let input_tokens: TokenStream2 = match &input.data { +pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 { + let mut functions_to_generate: Vec = vec![]; + + let functions_to_export_in_lua: TokenStream2 = match &input.data { syn::Data::Struct(input) => match &input.fields { syn::Fields::Named(named_fields) => named_fields .named .iter() .map(|field| -> TokenStream2 { - let field_ident = field - .ident - .as_ref() - .expect("These are only the named field, thus they all should have a name."); - - let enum_variant_type = match &field.ty { - syn::Type::BareFn(function) => { - let return_path: &ReturnType = &function.output; - match return_path { - ReturnType::Default => None, - ReturnType::Type(_, return_type) => Some(match *(return_type.to_owned()) { - syn::Type::Path(path_type) => path_type - .path - .get_ident() - .expect("A path should either be complete, or only conain one segment") - .to_owned(), - _ => unimplemented!("This is only implemented for path types"), - }), + let input_type = match &field.ty { + syn::Type::BareFn(bare_fn) => { + if bare_fn.inputs.len() == 1 { + bare_fn.inputs.last().expect("The last element exists").ty.clone() + } else if bare_fn.inputs.len() == 0 { + let input_type: Type = parse_quote! {()}; + input_type + } else { + panic!("The Function can only take on argument, or none"); } } _ => unimplemented!("This is only implemented for bare function types"), }; + let return_type = match &field.ty { + syn::Type::BareFn(function) => { + let return_path: &ReturnType = &function.output; + match return_path { + ReturnType::Default => None, + ReturnType::Type(_, return_type) => Some(return_type.to_owned()) } + } + _ => unimplemented!("This is only implemented for bare function types"), + }; - let enum_variant_name = format_ident!( - "{}", - field_ident.to_string().from_case(Case::Snake).to_case(Case::Pascal) - ); - if enum_variant_type.is_some() { - quote! { - #enum_variant_name (#enum_variant_type), + let function_name = field + .ident + .as_ref() + .expect("These are only the named field, thus they all should have a name."); + + if let Some(ret_type) = return_type { + functions_to_generate.push(quote! { + #[ci_command] + async fn #function_name(lua: &mlua::Lua, input: #input_type) -> Result<#ret_type, mlua::Error> { } - .into() + }); } else { - quote! { - #enum_variant_name, + functions_to_generate.push(quote! { + #[ci_command] + async fn #function_name(lua: &mlua::Lua, input: #input_type) -> Result<(), mlua::Error> { } + }); } + generate_ci_function_exposure(field) }) .collect(), + _ => unimplemented!("Only implemented for named fileds"), }, _ => unimplemented!("Only implemented for structs"), }; + let functions_to_generate: TokenStream2 = functions_to_generate.into_iter().collect(); let gen = quote! { - #[derive(Debug)] - pub enum Command { - #input_tokens + pub fn generate_ci_functions( + lua: &mut mlua::Lua, + tx: tokio::sync::mpsc::Sender + ) + { + lua.set_app_data(tx); + let globals = lua.globals(); + #functions_to_export_in_lua } + #functions_to_generate }; gen.into() } diff --git a/lua_macros/src/struct_to_ci_enum/generate_help_function.rs b/lua_macros/src/struct_to_ci_enum/generate_help_function.rs new file mode 100644 index 0000000..2987272 --- /dev/null +++ b/lua_macros/src/struct_to_ci_enum/generate_help_function.rs @@ -0,0 +1,53 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::{quote, ToTokens}; + +pub fn generate_help_function(input: &syn::FieldsNamed) -> TokenStream2 { + let input: Vec<_> = input.named.iter().collect(); + + let combined_help_text: TokenStream2 = input + .iter() + .map(|field| { + let attrs_with_doc: Vec = field + .attrs + .iter() + .filter_map(|attr| { + if attr.path().is_ident("doc") { + let help_text = attr + .meta + .require_name_value() + .expect("This is a named value type, because all doc comments work this way") + .value + .clone(); + Some(help_text.into_token_stream().into()) + } else { + return None; + } + }) + .collect(); + if attrs_with_doc.len() == 0 { + // TODO there should be a better panic function, than the generic one + panic!( + "The command named: `{}`, does not provide a help message", + field.ident.as_ref().expect("These are all named") + ); + } else { + let help_text_for_one_command_combined: TokenStream2 = attrs_with_doc.into_iter().collect(); + return help_text_for_one_command_combined; + } + }) + .collect(); + + quote! { + #[ci_command] + async fn help( + lua: &mlua::Lua, + input_str: Option + ) -> Result { + // TODO add a way to filter the help based on the input + + let output = "These functions exist:\n"; + output.push_str(#combined_help_text); + Ok(output) + } + } +} diff --git a/lua_macros/src/struct_to_ci_enum/mod.rs b/lua_macros/src/struct_to_ci_enum/mod.rs new file mode 100644 index 0000000..4d93f98 --- /dev/null +++ b/lua_macros/src/struct_to_ci_enum/mod.rs @@ -0,0 +1,7 @@ +pub mod generate_command_enum; +pub mod generate_generate_ci_function; +pub mod generate_help_function; + +pub use generate_command_enum::*; +pub use generate_generate_ci_function::*; +pub use generate_help_function::*; -- 2.40.1 From 20c751fd7fff9d1409e1e0d4b2d7cbcd88f94702 Mon Sep 17 00:00:00 2001 From: Soispha Date: Thu, 20 Jul 2023 21:49:23 +0200 Subject: [PATCH 17/18] Fix(treewide): Update codebase to new lua_macros api --- Cargo.toml | 2 +- src/app/command_interface.rs | 27 ++++-------- .../event_types/event/handlers/command.rs | 42 ++++++++++++++---- .../event_types/event/handlers/lua_command.rs | 44 ++++++++++++------- .../events/event_types/event/handlers/main.rs | 15 ++++--- src/app/events/event_types/event/mod.rs | 37 ++++++++++------ src/app/mod.rs | 10 ++--- 7 files changed, 108 insertions(+), 69 deletions(-) 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"); -- 2.40.1 From a6d176b6e9420f5fe89fb47cc6dc7593032874c4 Mon Sep 17 00:00:00 2001 From: Soispha Date: Thu, 20 Jul 2023 21:50:30 +0200 Subject: [PATCH 18/18] Fix(event_handlers/ci_output): Remove because not needed anymore --- .../events/event_types/event/handlers/ci_output.rs | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 src/app/events/event_types/event/handlers/ci_output.rs diff --git a/src/app/events/event_types/event/handlers/ci_output.rs b/src/app/events/event_types/event/handlers/ci_output.rs deleted file mode 100644 index 9df9da4..0000000 --- a/src/app/events/event_types/event/handlers/ci_output.rs +++ /dev/null @@ -1,11 +0,0 @@ -use anyhow::Result; -use cli_log::info; - -use crate::app::{events::event_types::EventStatus, App}; - -pub async fn handle(app: &mut App<'_>, output: &String) -> Result { - info!("Recieved command output: `{}`", output); - app.ui.set_command_output(output); - - Ok(EventStatus::Ok) -} -- 2.40.1