Compare commits

..

4 Commits

16 changed files with 309 additions and 126 deletions

10
Cargo.lock generated
View File

@ -415,15 +415,6 @@ 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"
@ -1300,7 +1291,6 @@ dependencies = [
name = "lua_macros"
version = "0.1.0"
dependencies = [
"convert_case",
"proc-macro2 1.0.64",
"quote 1.0.29",
"syn 2.0.25",

View File

@ -45,14 +45,10 @@
overlays = [(import rust-overlay)];
};
nightly = true;
#rust-nightly = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default);
rust-stable = pkgs.rust-bin.stable.latest.default;
rust =
if nightly
then pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default)
else pkgs.rust-bin.stable.latest.default;
craneLib = (crane.mkLib pkgs).overrideToolchain rust;
craneLib = (crane.mkLib pkgs).overrideToolchain rust-stable;
nativeBuildInputs = with pkgs; [
pkg-config
@ -82,7 +78,7 @@
statix
ltex-ls
rust
rust-stable
rust-analyzer
cargo-edit
cargo-expand

View File

@ -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
}

View File

@ -1,12 +1,14 @@
mod struct_to_ci_enum;
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::quote;
use struct_to_ci_enum::{generate_command_enum, generate_generate_ci_function};
use syn::{self, ItemFn};
use syn::{self, ItemFn, Field, parse::Parser};
#[proc_macro_attribute]
pub fn turn_struct_to_ci_commands(_attrs: TokenStream, input: TokenStream) -> TokenStream {
@ -27,6 +29,26 @@ pub fn turn_struct_to_ci_commands(_attrs: TokenStream, input: TokenStream) -> To
.into()
}
/// 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.");
// 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 {

View File

@ -1,12 +1,7 @@
use convert_case::{Case, Casing};
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote, ToTokens};
use syn::{
braced,
punctuated::Punctuated,
token::{Comma, Semi},
Block, Expr, FnArg, Stmt, Token,
};
use syn::{Block, Expr, ExprBlock, GenericArgument, ReturnType, Stmt, Type};
pub fn generate_final_function(input: &mut syn::ItemFn) -> TokenStream2 {
append_tx_send_code(input);
@ -29,23 +24,137 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn {
.to_case(Case::Pascal)
);
let tx_send = quote! {
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 = context.named_registry_value("sender_for_ci_commands");
let tx: std::sync::mpsc::Sender<crate::app::events::event_types::Event> =
context
.named_registry_value("sender_for_ci_commands")
.expect("This exists, it was set before");
tx
.send(Event::CommandEvent(Commands::#function_name_pascal))
.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 <String, rlua::Error>)
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<crate::app::events::event_types::Event> =
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<crate::app::events::event_types::Event> =
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 content;
braced!(content in tx_send);
let mut tx_send_expr: Vec<Stmt> =
Block::parse_within(content).expect("This is a static string, it will always parse");
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<Stmt> = Vec::with_capacity(input.block.stmts.len() + tx_send_expr.len());
new_stmts.append(&mut tx_send_expr);
let mut new_stmts: Vec<Stmt> = 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

View File

@ -4,15 +4,60 @@ 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<TokenStream2> = 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 {
let field_ident = field.ident.as_ref().expect(
"These are only the named field, thus they all should have a name.",
);
if field.attrs.iter().any(|attribute| {
attribute.path()
== &syn::parse_str::<syn::Path>("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<crate::app::events::event_types::Event>)
{
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! {
@ -32,23 +77,6 @@ pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 {
);
}
.into()
})
.collect(),
_ => unimplemented!("Only implemented for named fileds"),
},
_ => unimplemented!("Only implemented for structs"),
};
let gen = quote! {
pub fn generate_ci_functions(
context: &mut rlua::Context,
tx: std::sync::mpsc::Sender<crate::app::events::event_types::Event>)
{
let globals = context.globals();
#input_tokens
}
};
gen.into()
}
pub fn generate_command_enum(input: &syn::DeriveInput) -> TokenStream2 {
@ -103,7 +131,8 @@ pub fn generate_command_enum(input: &syn::DeriveInput) -> TokenStream2 {
};
let gen = quote! {
pub enum Commands {
#[derive(Debug)]
pub enum Command {
#input_tokens
}
};

View File

@ -1,4 +1,5 @@
use lua_macros::{turn_struct_to_ci_commands, ci_command};
// 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;
use super::events::event_types::Event;
@ -24,24 +25,28 @@ struct Commands<'lua> {
greet: fn(usize) -> String,
// Closes the application
#[gen_default_lua_function]
exit: fn(),
//command_line_show: fn(),
//command_line_hide: fn(),
#[gen_default_lua_function]
command_line_show: fn(),
#[gen_default_lua_function]
command_line_hide: fn(),
//cycle_planes: fn(),
//cycle_planes_rev: 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),
room_message_send: fn(String) -> String,
}
#[ci_command]
fn greet(_context: Context, name: String) -> Result<String, rlua::Error> {
Ok(format!("Name is {}", name))
fn greet(context: Context, input_str: String) -> Result<String, rlua::Error> {
Ok(format!("Name is {}", input_str))
}
#[ci_command]
fn exit(_context: Context, _input_str: String) -> Result<(), rlua::Error> {
Event::CommandEvent(Commands::Exit);
Ok(())
fn room_message_send(context: Context, input_str: String) -> Result<String, rlua::Error> {
Ok(format!("Sent message: {}", input_str))
}

View File

@ -1,15 +1,11 @@
use anyhow::{bail, Context, Result};
use cli_log::warn;
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<EventStatus> {
info!("Recieved command output: `{}`");
if let Some(cli) = app.ui.cli {
cli.
} else {
warn!("There is no way to display the output!");
}
pub async fn handle(app: &mut App<'_>, output: &String) -> Result<EventStatus> {
info!("Recieved command output: `{}`", output);
app.ui.set_command_output(output);
Ok(EventStatus::Ok)
}

View File

@ -1,34 +1,36 @@
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;
pub async fn handle(app: &mut App<'_>, command: &Command) -> Result<EventStatus> {
info!("Handling command: {:#?}", command);
match command {
Command::Exit => return Ok(EventStatus::Terminate),
Ok(match command {
Command::Exit => EventStatus::Terminate,
Command::CommandLineShow => {
app.ui.cli_enable();
EventStatus::Ok
}
Command::CommandLineHide => {
app.ui.cli_disable();
EventStatus::Ok
}
Command::CyclePlanes => {
app.ui.cycle_main_input_position();
EventStatus::Ok
}
Command::CyclePlanesRev => {
app.ui.cycle_main_input_position_rev();
EventStatus::Ok
}
Command::RoomMessageSend(msg) => {
if let Some(room) = app.status.room_mut() {
room.send(msg.clone()).await?;
} else {
// FIXME: This needs a callback to return an error.
}
EventStatus::Ok
}
}
Ok(EventStatus::Ok)
})
}

View File

@ -2,7 +2,11 @@ use anyhow::Result;
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers};
use crate::{
app::{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<E
CrosstermEvent::Key(KeyEvent {
code: KeyCode::Esc, ..
}) => {
app.transmitter.send(Event::CommandEvent(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,10 +51,10 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
modifiers: KeyModifiers::ALT,
..
}) => {
command::execute(
app.channel_tx(),
Command::RoomMessageSend(app.ui.message_compose.lines().join("\n")),
)
app.transmitter
.send(Event::CommandEvent(Command::RoomMessageSend(
app.ui.message_compose.lines().join("\n"),
)))
.await?;
app.ui.message_compose_clear();
}
@ -153,7 +165,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
"There can only be one line in the buffer, as we collect it on enter being inputted"
)
.to_owned();
let output = (&cli_event).await?;
let output = app.handle_ci_event(&cli_event).await?;
// delete the old text:

View File

@ -1,6 +1,4 @@
pub mod command;
pub mod lua_command;
pub mod main;
pub mod matrix;
pub mod setup;
pub mod ci_output;

View File

@ -3,9 +3,9 @@ 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, lua_command, main, matrix, setup};
use self::handlers::{command, main, matrix, setup};
use super::EventStatus;
@ -14,21 +14,11 @@ pub enum Event {
InputEvent(CrosstermEvent),
MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse),
CommandEvent(Command),
LuaCommand(String),
CiOutput(String),
}
impl Event {
pub async fn handle(&self, app: &mut App<'_>) -> Result<EventStatus> {
match &self {
Event::LuaCommand(command) => lua_command::handle(app, command)
.await
.with_context(|| format!("Failed to handle lua command: `{:#?}`", command)),
Event::CiOutput(output) => ci_output::handle(app, output)
.await
.with_context(|| format!("Failed to handle ci output: `{:#?}`", output)),
Event::MatrixEvent(event) => matrix::handle(app, event)
.await
.with_context(|| format!("Failed to handle matrix event: `{:#?}`", event)),

View File

@ -1,11 +1,6 @@
#[derive(Debug)]
pub enum EventStatus {
/// The event was handled successfully
Ok,
/// TODO
Finished,
/// Terminate the whole application
Terminate,
}

View File

@ -3,14 +3,13 @@ pub mod events;
pub mod status;
pub mod transmitter;
use std::path::Path;
use std::{path::Path, sync::mpsc::Sender as StdSender};
use anyhow::{Context, Error, Result};
use cli_log::info;
use matrix_sdk::Client;
use rlua::Lua;
use status::{State, Status};
use tokio::sync::mpsc::Sender;
use tokio_util::sync::CancellationToken;
use crate::{
@ -35,7 +34,7 @@ pub struct App<'ui> {
impl App<'_> {
pub fn new() -> Result<Self> {
fn set_up_lua(tx: Sender<Event>) -> Lua {
fn set_up_lua(tx: StdSender<Event>) -> Lua {
let lua = Lua::new();
lua.context(|mut lua_context| {
@ -62,10 +61,25 @@ impl App<'_> {
input_listener_killer: CancellationToken::new(),
matrix_listener_killer: CancellationToken::new(),
lua: set_up_lua(transmitter.tx()),
lua: set_up_lua(transmitter.std_tx()),
})
}
pub async fn handle_ci_event(&self, event: &str) -> Result<String> {
info!("Recieved ci event: `{event}`; executing..");
// TODO: Should the ci support more than strings?
let output = self.lua.context(|context| -> Result<String> {
let output = context
.load(&event)
.eval::<String>()
.with_context(|| format!("Failed to execute: `{event}`"))?;
info!("Function evaluated to: `{output}`");
Ok(output)
})?;
Ok(output)
}
pub async fn run(&mut self) -> Result<()> {
// Spawn input event listener
tokio::task::spawn(events::poll_input_events(
@ -93,8 +107,8 @@ impl App<'_> {
match event.handle(self).await? {
event_types::EventStatus::Ok => (),
event_types::EventStatus::Finished => (),
event_types::EventStatus::Terminate => break,
_ => (),
};
}

View File

@ -1,5 +1,5 @@
use anyhow::{Error, Result};
use cli_log::{info, warn};
use cli_log::{warn, info};
use indexmap::IndexMap;
use matrix_sdk::{
room::MessagesOptions,
@ -62,7 +62,7 @@ impl Room {
let events = self.matrix_room.messages(messages_options).await?;
self.timeline_end = events.end;
for event in events.chunk {
for event in events.chunk.iter() {
self.timeline.insert(
0,
match event.event.deserialize() {

View File

@ -1,3 +1,5 @@
use std::sync::mpsc as StdMpsc;
use anyhow::{bail, Context, Result};
use tokio::sync::mpsc;
@ -6,17 +8,29 @@ use super::events::event_types::Event;
pub struct Transmitter {
tx: mpsc::Sender<Event>,
rx: mpsc::Receiver<Event>,
std_tx: StdMpsc::Sender<Event>,
std_rx: StdMpsc::Receiver<Event>,
}
impl Transmitter {
pub fn new() -> Transmitter {
let (std_tx, std_rx) = StdMpsc::channel();
let (tx, rx) = mpsc::channel(256);
Transmitter { tx, rx }
Transmitter {
tx,
rx,
std_tx,
std_rx,
}
}
pub fn tx(&self) -> mpsc::Sender<Event> {
self.tx.to_owned()
}
pub fn std_tx(&self) -> StdMpsc::Sender<Event> {
self.std_tx.to_owned()
}
pub async fn recv(&mut self) -> Result<Event> {
match self.rx.recv().await {
Some(event) => Ok(event),