Compare commits

..

No commits in common. "6745da4c715f56ac314429c3f567f64f36e3492a" and "29aa6c1d3365d649e149928038c94b3c46c5a740" have entirely different histories.

27 changed files with 318 additions and 617 deletions

View File

@ -1,96 +1,50 @@
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote, ToTokens}; use quote::{format_ident, quote};
use syn::{punctuated::Punctuated, Token, Type, Ident}; use syn::{DeriveInput, Field, Type};
use crate::{DataCommandEnum, Field}; use super::{get_input_type_of_bare_fn_field, parse_derive_input_as_named_fields};
use super::get_input_type_of_bare_fn_field; pub fn command_enum(input: &DeriveInput) -> TokenStream2 {
let named_fields = parse_derive_input_as_named_fields(input);
pub fn command_enum(input: &DataCommandEnum) -> TokenStream2 { let fields: TokenStream2 = named_fields
let (fields, namespace_enums): (TokenStream2, TokenStream2) = .named
turn_fields_to_enum(&input.fields); .iter()
.map(|field| turn_struct_fieled_to_enum(field))
.collect();
quote! { quote! {
#[derive(Debug)] #[derive(Debug)]
pub enum Command { pub enum Command {
#fields #fields
} }
#namespace_enums
} }
} }
fn turn_fields_to_enum(fields: &Punctuated<Field, Token![,]>) -> (TokenStream2, TokenStream2) { fn turn_struct_fieled_to_enum(field: &Field) -> TokenStream2 {
let output: Vec<_> = fields let field_name = format_ident!(
.iter() "{}",
.map(|field| turn_struct_field_to_enum(field)) field
.collect(); .ident
.as_ref()
.expect("These are named fields, it should be Some(<name>)")
.to_string()
.from_case(Case::Snake)
.to_case(Case::Pascal)
);
let mut fields_output: TokenStream2 = Default::default(); let input_type: Option<Type> = get_input_type_of_bare_fn_field(field);
let mut namespace_enums_output: TokenStream2 = Default::default();
for (fields, namespace_enum) in output { match input_type {
fields_output.extend(fields.to_token_stream()); Some(input_type) => {
namespace_enums_output.extend(namespace_enum.to_token_stream()); quote! {
} #field_name(#input_type),
(fields_output, namespace_enums_output)
}
fn turn_struct_field_to_enum(field: &Field) -> (TokenStream2, TokenStream2) {
match field {
Field::Function(fun_field) => {
let field_name = format_ident!(
"{}",
fun_field
.name
.to_string()
.from_case(Case::Snake)
.to_case(Case::Pascal)
);
let input_type: Option<Type> = get_input_type_of_bare_fn_field(fun_field);
match input_type {
Some(input_type) => (
quote! {
#field_name(#input_type),
},
quote! {},
),
None => (
quote! {
#field_name,
},
quote! {},
),
} }
} }
Field::Namespace(namespace) => { None => {
let (namespace_output_fields, namespace_output_namespace_enums) = quote! {
turn_fields_to_enum(&namespace.fields); #field_name,
let namespace_name: Ident = format_ident!( }
"{}",
namespace.path.iter().map(|name| name.to_string()).collect::<String>()
);
let new_namespace_name: Ident = format_ident!(
"{}",
namespace_name.to_string().from_case(Case::Snake).to_case(Case::Pascal)
);
(
quote! {
#new_namespace_name(#new_namespace_name),
},
quote! {
#[derive(Debug)]
pub enum #new_namespace_name {
#namespace_output_fields
}
#namespace_output_namespace_enums
},
)
} }
} }
} }

View File

@ -1,35 +1,16 @@
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use syn::{punctuated::Punctuated, Token}; use syn::{DeriveInput, Field};
use crate::{DataCommandEnum, Field, FunctionDeclaration, NamespacePath}; use crate::generate::parse_derive_input_as_named_fields;
pub fn generate_add_lua_functions_to_globals(input: &DataCommandEnum) -> TokenStream2 { pub fn generate_add_lua_functions_to_globals(input: &DeriveInput) -> TokenStream2 {
fn turn_field_to_functions( let named_fields = parse_derive_input_as_named_fields(input);
input: &Punctuated<Field, Token![,]>, let function_adders: TokenStream2 = named_fields
namespace_path: Option<&NamespacePath>, .named
) -> TokenStream2 { .iter()
input .map(|field| generate_function_adder(field))
.iter() .collect();
.map(|field| match field {
crate::Field::Function(function) => {
generate_function_adder(function, namespace_path)
}
crate::Field::Namespace(namespace) => {
let mut passed_namespace =
namespace_path.unwrap_or(&Default::default()).clone();
namespace
.path
.clone()
.into_iter()
.for_each(|val| passed_namespace.push(val));
turn_field_to_functions(&namespace.fields, Some(&passed_namespace))
}
})
.collect()
}
let function_adders: TokenStream2 = turn_field_to_functions(&input.fields, None);
quote! { quote! {
pub fn add_lua_functions_to_globals( pub fn add_lua_functions_to_globals(
@ -47,67 +28,15 @@ pub fn generate_add_lua_functions_to_globals(input: &DataCommandEnum) -> TokenSt
} }
} }
fn generate_function_adder( fn generate_function_adder(field: &Field) -> TokenStream2 {
field: &FunctionDeclaration, let field_ident = field
namespace_path: Option<&NamespacePath>, .ident
) -> TokenStream2 { .as_ref()
let field_ident = &field.name; .expect("This is should be a named field");
let function_ident = format_ident!("wrapped_lua_function_{}", field_ident); let function_ident = format_ident!("wrapped_lua_function_{}", field_ident);
let function_name = field_ident.to_string(); let function_name = field_ident.to_string();
let setter = if let Some(namespace_path) = namespace_path {
// ```lua
// local globals = {
// ns1: {
// ns_value,
// ns_value2,
// },
// ns2: {
// ns_value3,
// }
// }
// ns1.ns_value
// ```
let mut counter = 0;
let namespace_table_gen: TokenStream2 = namespace_path.iter().map(|path| {
let path = path.to_string();
counter += 1;
let mut set_function: TokenStream2 = Default::default();
if counter == namespace_path.len() {
set_function = quote! {
table.set(#function_name, #function_ident).expect(
"Setting a static global value should work"
);
};
}
quote! {
let table: mlua::Table = {
if table.contains_key(#path).expect("This check should work") {
let table2 = table.get(#path).expect("This was already checked");
table2
} else {
table.set(#path, lua.create_table().expect("This should also always work")).expect("Setting this value should work");
table.get(#path).expect("This was set, just above")
}
};
#set_function
}
}).collect();
quote! {
let table = &globals;
{
#namespace_table_gen
}
}
} else {
quote! {
globals.set(#function_name, #function_ident).expect(
"Setting a static global value should work"
);
}
};
quote! { quote! {
{ {
let #function_ident = lua.create_async_function(#field_ident).expect( let #function_ident = lua.create_async_function(#field_ident).expect(
@ -116,7 +45,10 @@ fn generate_function_adder(
#function_name #function_name
) )
); );
#setter
globals.set(#function_name, #function_ident).expect(
"Setting a static global value should work"
);
} }
} }
} }

View File

@ -1,20 +1,18 @@
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::quote; use quote::quote;
use syn::DeriveInput;
use crate::{ use crate::generate::lua_wrapper::{
generate::lua_wrapper::{ lua_functions_to_globals::generate_add_lua_functions_to_globals,
lua_functions_to_globals::generate_add_lua_functions_to_globals, rust_wrapper_functions::generate_rust_wrapper_functions,
rust_wrapper_functions::generate_rust_wrapper_functions,
},
DataCommandEnum,
}; };
mod lua_functions_to_globals; mod lua_functions_to_globals;
mod rust_wrapper_functions; mod rust_wrapper_functions;
pub fn lua_wrapper(input: &DataCommandEnum) -> TokenStream2 { pub fn lua_wrapper(input: &DeriveInput) -> TokenStream2 {
let add_lua_functions_to_globals = generate_add_lua_functions_to_globals(input); let add_lua_functions_to_globals = generate_add_lua_functions_to_globals(input);
let rust_wrapper_functions = generate_rust_wrapper_functions(None, input); let rust_wrapper_functions = generate_rust_wrapper_functions(input);
quote! { quote! {
#add_lua_functions_to_globals #add_lua_functions_to_globals
#rust_wrapper_functions #rust_wrapper_functions

View File

@ -1,39 +1,21 @@
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::quote; use quote::{format_ident, quote};
use syn::{punctuated::Punctuated, token::Comma, GenericArgument, Lifetime, Token, Type}; use syn::{
punctuated::Punctuated, token::Comma, DeriveInput, Field, GenericArgument, Lifetime, Type,
use crate::{
generate::{get_input_type_of_bare_fn_field, get_return_type_of_bare_fn_field},
DataCommandEnum, Field, FunctionDeclaration, NamespacePath,
}; };
pub fn generate_rust_wrapper_functions( use crate::generate::{
namespace: Option<&NamespacePath>, get_input_type_of_bare_fn_field, get_return_type_of_bare_fn_field,
input: &DataCommandEnum, parse_derive_input_as_named_fields,
) -> TokenStream2 { };
generate_rust_wrapper_functions_rec(namespace, &input.fields)
}
pub fn generate_rust_wrapper_functions_rec( pub fn generate_rust_wrapper_functions(input: &DeriveInput) -> TokenStream2 {
namespace: Option<&NamespacePath>, let named_fields = parse_derive_input_as_named_fields(input);
input: &Punctuated<Field, Token![,]>, let wrapped_functions: TokenStream2 = named_fields
) -> TokenStream2 { .named
let wrapped_functions: TokenStream2 = input
.iter() .iter()
.map(|field| match field { .map(|field| wrap_lua_function(field))
Field::Function(fun_field) => {
wrap_lua_function(namespace.unwrap_or(&Default::default()), fun_field)
}
Field::Namespace(nasp) => {
let mut passed_namespace = namespace.unwrap_or(&Default::default()).clone();
nasp.path
.clone()
.into_iter()
.for_each(|val| passed_namespace.push(val));
generate_rust_wrapper_functions_rec(Some(&passed_namespace), &nasp.fields)
}
})
.collect(); .collect();
quote! { quote! {
@ -41,12 +23,12 @@ pub fn generate_rust_wrapper_functions_rec(
} }
} }
fn wrap_lua_function(namespace: &NamespacePath, field: &FunctionDeclaration) -> TokenStream2 { fn wrap_lua_function(field: &Field) -> TokenStream2 {
let input_type = get_input_type_of_bare_fn_field(field); let input_type = get_input_type_of_bare_fn_field(field);
let return_type = get_return_type_of_bare_fn_field(field); let return_type = get_return_type_of_bare_fn_field(field);
let function_name = &field.name; let function_name = field.ident.as_ref().expect("This should be a named field");
let function_body = get_function_body(&namespace, field, input_type.is_some(), &return_type); let function_body = get_function_body(field, input_type.is_some(), &return_type);
let lifetime_args = let lifetime_args =
get_and_add_lifetimes_form_inputs_and_outputs(input_type.clone(), return_type); get_and_add_lifetimes_form_inputs_and_outputs(input_type.clone(), return_type);
@ -116,60 +98,30 @@ fn get_and_add_lifetimes_form_inputs_and_outputs<'a>(
output output
} }
fn get_function_body( fn get_function_body(field: &Field, has_input: bool, output_type: &Option<Type>) -> TokenStream2 {
namespace: &NamespacePath,
field: &FunctionDeclaration,
has_input: bool,
output_type: &Option<Type>,
) -> TokenStream2 {
let command_name = field let command_name = field
.name .ident
.as_ref()
.expect("These are named fields, it should be Some(<name>)")
.to_string() .to_string()
.from_case(Case::Snake) .from_case(Case::Snake)
.to_case(Case::Pascal); .to_case(Case::Pascal);
let command_ident = format_ident!("{}", command_name);
let command_ident = { let send_output = if has_input {
if has_input { quote! {
format!("{}(", command_name) Event::CommandEvent(
} else { Command::#command_ident(input.clone()),
command_name.clone() Some(callback_tx),
)
}
} else {
quote! {
Event::CommandEvent(
Command::#command_ident,
Some(callback_tx),
)
} }
};
let command_namespace: String = {
namespace
.iter()
.map(|path| {
let path_enum_name: String = path
.to_string()
.from_case(Case::Snake)
.to_case(Case::Pascal);
path_enum_name.clone() + "(" + &path_enum_name + "::"
})
.collect::<Vec<String>>()
.join("")
};
let send_output: TokenStream2 = {
let finishing_brackets = {
if has_input {
let mut output = "input.clone()".to_owned();
output.push_str(&(0..namespace.len()).map(|_| ')').collect::<String>());
output
} else {
(0..namespace.len()).map(|_| ')').collect::<String>()
}
};
("Event::CommandEvent( Command::".to_owned()
+ &command_namespace
+ &command_ident
+ &finishing_brackets
+ {if has_input {")"} else {""}} /* Needed as command_name opens one */
+ ",Some(callback_tx))")
.parse()
.expect("This code should be valid")
}; };
let function_return = if let Some(_) = output_type { let function_return = if let Some(_) = output_type {

View File

@ -3,9 +3,18 @@ mod lua_wrapper;
pub use command_enum::command_enum; pub use command_enum::command_enum;
pub use lua_wrapper::lua_wrapper; pub use lua_wrapper::lua_wrapper;
use syn::{ReturnType, Type, TypeBareFn}; use syn::{DeriveInput, Field, FieldsNamed, ReturnType, Type, TypeBareFn};
use crate::FunctionDeclaration; pub fn parse_derive_input_as_named_fields(input: &DeriveInput) -> FieldsNamed {
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()
}
pub fn get_bare_fn_input_type(function: &TypeBareFn) -> Option<Type> { pub fn get_bare_fn_input_type(function: &TypeBareFn) -> Option<Type> {
if function.inputs.len() == 1 { if function.inputs.len() == 1 {
@ -28,7 +37,7 @@ pub fn get_bare_fn_input_type(function: &TypeBareFn) -> Option<Type> {
} }
} }
pub fn get_input_type_of_bare_fn_field(field: &FunctionDeclaration) -> Option<Type> { pub fn get_input_type_of_bare_fn_field(field: &Field) -> Option<Type> {
match &field.ty { match &field.ty {
syn::Type::BareFn(function) => get_bare_fn_input_type(&function), syn::Type::BareFn(function) => get_bare_fn_input_type(&function),
_ => unimplemented!( _ => unimplemented!(
@ -37,7 +46,7 @@ pub fn get_input_type_of_bare_fn_field(field: &FunctionDeclaration) -> Option<Ty
), ),
} }
} }
pub fn get_return_type_of_bare_fn_field(field: &FunctionDeclaration) -> Option<Type> { pub fn get_return_type_of_bare_fn_field(field: &Field) -> Option<Type> {
match &field.ty { match &field.ty {
syn::Type::BareFn(function) => get_bare_fn_return_type(&function), syn::Type::BareFn(function) => get_bare_fn_return_type(&function),
_ => unimplemented!( _ => unimplemented!(

View File

@ -1,10 +1,7 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::quote; use quote::quote;
use syn::{ use syn::DeriveInput;
braced, parse::Parse, parse_macro_input, punctuated::Punctuated, token, Attribute, Ident,
Token, Type,
};
mod generate; mod generate;
@ -17,17 +14,19 @@ mod generate;
/// the rust wrapper functions to the lua globals. /// the rust wrapper functions to the lua globals.
/// ///
/// The input and output values of the wrapped functions are derived from the values specified in /// The input and output values of the wrapped functions are derived from the values specified in
/// the input to the `parse_command_enum` proc macro. /// the `Commands` struct.
/// The returned values will be returned directly to the lua context, this allows to nest functions.
/// ///
/// For example this rust code: /// For example this rust code:
/// ```no_run /// ```rust
/// parse_command_enum! { /// #[ci_command_enum]
/// struct Commands {
/// /// Greets the user /// /// Greets the user
/// greet: fn(String) -> String, /// greet: fn(String) -> String,
/// } /// }
/// ``` /// ```
/// results in this expanded code: /// results in this expanded code:
/// ```no_run /// ```rust
/// #[derive(Debug)] /// #[derive(Debug)]
/// pub enum Command { /// pub enum Command {
/// Greet(String), /// Greet(String),
@ -73,118 +72,12 @@ mod generate;
/// }; /// };
/// } /// }
/// ``` /// ```
#[derive(Debug)] #[proc_macro_attribute]
struct DataCommandEnum { pub fn ci_command_enum(_attrs: TokenStream, input: TokenStream) -> TokenStream {
#[allow(dead_code)] // Construct a representation of Rust code as a syntax tree
commands_token: kw::commands, // that we can manipulate
let input: DeriveInput = syn::parse(input)
#[allow(dead_code)] .expect("This should always be valid rust code, as it's extracted from direct code");
brace_token: token::Brace,
fields: Punctuated<Field, Token![,]>,
}
mod kw {
syn::custom_keyword!(commands);
syn::custom_keyword!(namespace);
syn::custom_keyword!(declare);
}
#[derive(Debug)]
enum Field {
Function(FunctionDeclaration),
Namespace(Namespace),
}
#[derive(Debug)]
struct Namespace {
#[allow(dead_code)]
namespace_token: kw::namespace,
path: NamespacePath,
#[allow(dead_code)]
brace_token: token::Brace,
fields: Punctuated<Field, Token![,]>,
}
type NamespacePath = Punctuated<Ident, Token![::]>;
#[derive(Debug)]
struct FunctionDeclaration {
#[allow(dead_code)]
function_token: kw::declare,
name: Ident,
#[allow(dead_code)]
colon_token: Token![:],
ty: Type,
}
impl Parse for DataCommandEnum {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let content;
Ok(DataCommandEnum {
commands_token: input.parse()?,
brace_token: braced!(content in input),
fields: content.parse_terminated(Field::parse, Token![,])?,
})
}
}
impl Parse for Field {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if input.peek(Token![#]) {
// FIXME(@soispha): We ignore doc comments, which should probably be replaced by adding
// them to the output <2023-09-19>
let _output = input.call(Attribute::parse_outer).unwrap_or(vec![]);
let lookahead = input.lookahead1();
if lookahead.peek(kw::namespace) {
input.parse().map(Field::Namespace)
} else if lookahead.peek(kw::declare) {
input.parse().map(Field::Function)
} else {
Err(lookahead.error())
}
} else {
if lookahead.peek(kw::declare) {
input.parse().map(Field::Function)
} else if lookahead.peek(kw::namespace) {
input.parse().map(Field::Namespace)
} else {
Err(lookahead.error())
}
}
}
}
impl Parse for FunctionDeclaration {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(FunctionDeclaration {
function_token: input.parse()?,
name: input.parse()?,
colon_token: input.parse()?,
ty: input.parse()?,
})
}
}
impl Parse for Namespace {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let content;
Ok(Namespace {
namespace_token: input.parse()?,
path: NamespacePath::parse_separated_nonempty(input)?,
brace_token: braced!(content in input),
fields: content.parse_terminated(Field::parse, Token![,])?,
})
}
}
#[proc_macro]
pub fn parse_command_enum(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DataCommandEnum);
// Build the language wrappers // Build the language wrappers
let lua_wrapper: TokenStream2 = generate::lua_wrapper(&input); let lua_wrapper: TokenStream2 = generate::lua_wrapper(&input);
@ -192,9 +85,9 @@ pub fn parse_command_enum(input: TokenStream) -> TokenStream {
// Build the final enum // Build the final enum
let command_enum = generate::command_enum(&input); let command_enum = generate::command_enum(&input);
let output = quote! { quote! {
#command_enum #command_enum
#lua_wrapper #lua_wrapper
}; }
output.into() .into()
} }

View File

@ -43,7 +43,8 @@ impl AccountsManager {
return match config { return match config {
Some(s) => { Some(s) => {
info!("Loading serialized AccountsManager"); info!("Loading serialized AccountsManager");
let accounts_data: AccountsData = serde_json::from_str(&s)?; let accounts_data: AccountsData =
serde_json::from_str(&s)?;
let mut clients = Vec::new(); let mut clients = Vec::new();
clients.resize(accounts_data.accounts.len(), None); clients.resize(accounts_data.accounts.len(), None);
Ok(Self { Ok(Self {
@ -93,12 +94,12 @@ impl AccountsManager {
let session = match client.session() { let session = match client.session() {
Some(s) => s, Some(s) => s,
None => return Err(Error::msg("Failed to get session")), None => return Err(Error::msg("Failed to get session"))
}; };
let name = match client.account().get_display_name().await? { let name = match client.account().get_display_name().await? {
Some(n) => n, Some(n) => n,
None => return Err(Error::msg("Failed to get display name")), None => return Err(Error::msg("Failed to get display name"))
}; };
let account = Account { let account = Account {

View File

@ -1,73 +0,0 @@
// Use `cargo expand app::command_interface::command_list` for an overview of the file contents
use language_macros::parse_command_enum;
// TODO(@soispha): Should these paths be moved to the proc macro?
// As they are not static, it could be easier for other people,
// if they stay here.
use mlua::IntoLua;
use crate::app::command_interface::command_transfer_value::CommandTransferValue;
use crate::app::Event;
parse_command_enum! {
commands {
/// Prints to the output, with a newline.
// HACK(@soispha): The stdlib Lua `print()` function has stdout as output hardcoded,
// redirecting stdout seems too much like a hack thus we are just redefining the print function
// to output to a controlled output. <2023-09-09>
declare print: fn(CommandTransferValue),
namespace trinitrix {
/// Debug only functions, these are effectively useless
namespace debug {
/// Greets the user
declare greet: fn(String) -> String,
/// Returns a table of greeted users
declare greet_multiple: fn() -> Table,
},
/// General API to change stuff in Name
namespace api {
/// Closes the application
declare exit: fn(),
/// Send a message to the current room
/// The send message is interpreted literally.
declare room_message_send: fn(String),
/// Open the help pages at the first occurrence of
/// the input string if it is Some, otherwise open
/// the help pages at the start
declare help: fn(Option<String>),
/// Function that change the UI, or UI state
namespace ui {
/// Shows the command line
declare command_line_show: fn(),
/// Hides the command line
declare command_line_hide: fn(),
/// Go to the next plane
declare cycle_planes: fn(),
/// Go to the previous plane
declare cycle_planes_rev: fn(),
/// Sets the current app mode to Normal / navigation mode
declare set_mode_normal: fn(),
/// Sets the current app mode to Insert / editing mode
declare set_mode_insert: fn(),
},
/// Functions only used internally within Name
namespace raw {
/// Send an error to the default error output
declare raise_error: fn(String),
/// Send output to the default output
/// This is mainly used to display the final
/// output of evaluated lua commands.
declare display_output: fn(String),
},
},
},
}
}

View File

@ -1,5 +1,6 @@
use std::{collections::HashMap, fmt::Display}; use std::{collections::HashMap, fmt::Display};
use mlua::FromLua;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub mod type_conversions; pub mod type_conversions;

View File

@ -11,13 +11,7 @@ use tokio::{
}; };
use crate::app::{ use crate::app::{
command_interface::{ command_interface::{add_lua_functions_to_globals, Command},
add_lua_functions_to_globals,
Api::Raw,
Command,
Raw::{DisplayOutput, RaiseError},
Trinitrix::Api,
},
events::event_types::Event, events::event_types::Event,
}; };
@ -93,10 +87,7 @@ async fn exec_lua(lua_code: &str, event_call_tx: mpsc::Sender<Event>) -> Result<
if output != "nil" { if output != "nil" {
event_call_tx event_call_tx
.send(Event::CommandEvent( .send(Event::CommandEvent(Command::DisplayOutput(output), None))
Command::Trinitrix(Api(Raw(DisplayOutput(output)))),
None,
))
.await .await
.context("Failed to send lua output command")? .context("Failed to send lua output command")?
} }
@ -105,7 +96,7 @@ async fn exec_lua(lua_code: &str, event_call_tx: mpsc::Sender<Event>) -> Result<
error!("Lua code `{}` returned error: `{}`", lua_code, err); error!("Lua code `{}` returned error: `{}`", lua_code, err);
event_call_tx event_call_tx
.send(Event::CommandEvent( .send(Event::CommandEvent(
Command::Trinitrix(Api(Raw(RaiseError(err.to_string())))), Command::RaiseError(err.to_string()),
None, None,
)) ))
.await?; .await?;

View File

@ -1,5 +1,64 @@
pub mod command_list; // Use `cargo expand app::command_interface` for an overview of the file contents
pub mod command_transfer_value; pub mod command_transfer_value;
pub mod lua_command_manager; pub mod lua_command_manager;
pub use command_list::*; use language_macros::ci_command_enum;
// TODO(@Soispha): Should these paths be moved to the proc macro?
// As they are not static, it could be easier for other people,
// if they stay here.
use crate::app::command_interface::command_transfer_value::CommandTransferValue;
use crate::app::Event;
use mlua::IntoLua;
#[ci_command_enum]
struct Commands {
/// Prints to the output, with a newline.
// HACK(@soispha): The stdlib Lua `print()` function has stdout as output hardcoded,
// redirecting stdout seems too much like a hack thus we are just redefining the print function
// to output to a controlled output. <2023-09-09>
print: fn(CommandTransferValue),
// Begin debug functions
/// Greets the user
greet: fn(String) -> String,
/// Returns a table of greeted users
greet_multiple: fn() -> Table,
// End debug functions
/// Closes the application
exit: fn(),
/// Shows the command line
command_line_show: fn(),
/// Hides the command line
command_line_hide: fn(),
/// Go to the next plane
cycle_planes: fn(),
/// Go to the previous plane
cycle_planes_rev: fn(),
/// Sets the current app mode to Normal / navigation mode
set_mode_normal: fn(),
/// Sets the current app mode to Insert / editing mode
set_mode_insert: fn(),
/// Send a message to the current room
/// The send message is interpreted literally.
room_message_send: fn(String),
/// Open the help pages at the first occurrence of
/// the input string if it is Some, otherwise open
/// the help pages at the start
help: fn(Option<String>),
/// Send an error to the default error output
raise_error: fn(String),
/// Send output to the default output
/// This is mainly used to display the final
/// output of evaluated lua commands.
display_output: fn(String),
}

View File

@ -8,7 +8,7 @@ use crate::{
app::{ app::{
command_interface::{ command_interface::{
command_transfer_value::{CommandTransferValue, Table}, command_transfer_value::{CommandTransferValue, Table},
Api, Command, Debug, Raw, Trinitrix, Ui, Command,
}, },
events::event_types::EventStatus, events::event_types::EventStatus,
status::State, status::State,
@ -66,102 +66,96 @@ pub async fn handle(
trace!("Handling command: {:#?}", command); trace!("Handling command: {:#?}", command);
Ok(match command { Ok(match command {
Command::Exit => {
send_status_output!("Terminating the application..");
EventStatus::Terminate
}
Command::DisplayOutput(output) => {
// TODO(@Soispha): This is only used to show the Lua command output to the user.
// Lua commands already receive the output. This should probably be communicated
// better, should it?
send_status_output!(output);
EventStatus::Ok
}
Command::Print(output) => { Command::Print(output) => {
let output_str: String = output.to_string(); let output_str: String = output.to_string();
send_status_output!(output_str); send_status_output!(output_str);
EventStatus::Ok EventStatus::Ok
} }
Command::Trinitrix(trinitrix) => match trinitrix { Command::CommandLineShow => {
Trinitrix::Debug(debug) => match debug { app.ui.cli_enable();
Debug::Greet(msg) => { app.status.set_state(State::Command);
send_main_output!("Greeting, {}!", msg); send_status_output!("CLI online");
EventStatus::Ok EventStatus::Ok
} }
Debug::GreetMultiple => { Command::CommandLineHide => {
let mut table: Table = HashMap::new(); app.ui.cli_disable();
table.insert("UserId".to_owned(), CommandTransferValue::Integer(2)); send_status_output!("CLI offline");
table.insert( EventStatus::Ok
"UserName".to_owned(), }
CommandTransferValue::String("James".to_owned()),
);
let mut second_table: Table = HashMap::new(); Command::CyclePlanes => {
second_table.insert("interface".to_owned(), CommandTransferValue::Integer(3)); app.ui.cycle_main_input_position();
second_table.insert("api".to_owned(), CommandTransferValue::Boolean(true)); send_status_output!("Switched main input position");
table.insert( EventStatus::Ok
"Versions".to_owned(), }
CommandTransferValue::Table(second_table), Command::CyclePlanesRev => {
); app.ui.cycle_main_input_position_rev();
send_main_output!(table); send_status_output!("Switched main input position; reversed");
EventStatus::Ok EventStatus::Ok
} }
},
Trinitrix::Api(api) => match api { Command::SetModeNormal => {
Api::Exit => { app.status.set_state(State::Normal);
send_status_output!("Terminating the application.."); send_status_output!("Set input mode to Normal");
EventStatus::Terminate EventStatus::Ok
} }
Api::RoomMessageSend(msg) => { Command::SetModeInsert => {
if let Some(room) = app.status.room_mut() { app.status.set_state(State::Insert);
room.send(msg.clone()).await?; app.ui.set_input_position(InputPosition::MessageCompose);
send_status_output!("Sent message: `{}`", msg); send_status_output!("Set input mode to Insert");
} else { EventStatus::Ok
// // FIXME(@soispha): This should raise an error within lua, as it would }
// otherwise be very confusing <2023-09-20>
warn!("Can't send message: `{}`, as there is no open room!", &msg); Command::RoomMessageSend(msg) => {
} if let Some(room) = app.status.room_mut() {
EventStatus::Ok room.send(msg.clone()).await?;
} send_status_output!("Sent message: `{}`", msg);
Api::Help(_) => todo!(), } else {
Api::Ui(ui) => match ui { // TODO(@Soispha): Should this raise a Lua error? It could be very confusing,
Ui::CommandLineShow => { // when a user doesn't read the log.
app.ui.cli_enable(); warn!("Can't send message: `{}`, as there is no open room!", &msg);
app.status.set_state(State::Command); }
send_status_output!("CLI online"); EventStatus::Ok
EventStatus::Ok }
} Command::Greet(name) => {
Ui::CommandLineHide => { send_main_output!("Hi, {}!", name);
app.ui.cli_disable(); EventStatus::Ok
send_status_output!("CLI offline"); }
EventStatus::Ok Command::GreetMultiple => {
} let mut table: Table = HashMap::new();
Ui::CyclePlanes => { table.insert("UserId".to_owned(), CommandTransferValue::Integer(2));
app.ui.cycle_main_input_position(); table.insert(
send_status_output!("Switched main input position"); "UserName".to_owned(),
EventStatus::Ok CommandTransferValue::String("James".to_owned()),
} );
Ui::CyclePlanesRev => {
app.ui.cycle_main_input_position_rev(); let mut second_table: Table = HashMap::new();
send_status_output!("Switched main input position; reversed"); second_table.insert("interface".to_owned(), CommandTransferValue::Integer(3));
EventStatus::Ok second_table.insert("api".to_owned(), CommandTransferValue::Boolean(true));
} table.insert(
Ui::SetModeNormal => { "Versions".to_owned(),
app.status.set_state(State::Normal); CommandTransferValue::Table(second_table),
send_status_output!("Set input mode to Normal"); );
EventStatus::Ok send_main_output!(table);
} EventStatus::Ok
Ui::SetModeInsert => { }
app.status.set_state(State::Insert); Command::Help(_) => todo!(),
app.ui.set_input_position(InputPosition::MessageCompose); Command::RaiseError(err) => {
send_status_output!("Set input mode to Insert"); send_error_output!(err);
EventStatus::Ok EventStatus::Ok
} }
},
Api::Raw(raw) => match raw {
Raw::RaiseError(err) => {
send_error_output!(err);
EventStatus::Ok
}
Raw::DisplayOutput(output) => {
// TODO(@Soispha): This is only used to show the Lua command output to the user.
// Lua commands already receive the output. This should probably be communicated
// better, should it?
send_status_output!(output);
EventStatus::Ok
}
},
},
},
}) })
} }

View File

@ -5,7 +5,10 @@ use crate::app::{events::event_types::EventStatus, App};
// This function is here mainly to reserve this spot for further processing of the lua command. // This function is here mainly to reserve this spot for further processing of the lua command.
// TODO(@Soispha): Move the lua executor thread code from app to this module // TODO(@Soispha): Move the lua executor thread code from app to this module
pub async fn handle(app: &mut App<'_>, command: String) -> Result<EventStatus> { pub async fn handle(
app: &mut App<'_>,
command: String,
) -> Result<EventStatus> {
trace!("Recieved ci command: `{command}`; executing.."); trace!("Recieved ci command: `{command}`; executing..");
app.lua.execute_code(command).await; app.lua.execute_code(command).await;

View File

@ -3,12 +3,7 @@ use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers}
use crate::{ use crate::{
app::{ app::{
command_interface::{ command_interface::Command,
Api::{Exit, Ui, RoomMessageSend},
Command,
Trinitrix::Api,
Ui::{CommandLineShow, CyclePlanes, CyclePlanesRev, SetModeInsert, SetModeNormal},
},
events::event_types::{Event, EventStatus}, events::event_types::{Event, EventStatus},
App, App,
}, },
@ -25,10 +20,7 @@ pub async fn handle_command(
code: KeyCode::Esc, .. code: KeyCode::Esc, ..
}) => { }) => {
app.tx app.tx
.send(Event::CommandEvent( .send(Event::CommandEvent(Command::SetModeNormal, None))
Command::Trinitrix(Api(Ui(SetModeNormal))),
None,
))
.await?; .await?;
} }
CrosstermEvent::Key(KeyEvent { CrosstermEvent::Key(KeyEvent {
@ -70,17 +62,14 @@ pub async fn handle_normal(app: &mut App<'_>, input_event: &CrosstermEvent) -> R
.. ..
}) => { }) => {
app.tx app.tx
.send(Event::CommandEvent(Command::Trinitrix(Api(Exit)), None)) .send(Event::CommandEvent(Command::Exit, None))
.await?; .await?;
} }
CrosstermEvent::Key(KeyEvent { CrosstermEvent::Key(KeyEvent {
code: KeyCode::Tab, .. code: KeyCode::Tab, ..
}) => { }) => {
app.tx app.tx
.send(Event::CommandEvent( .send(Event::CommandEvent(Command::CyclePlanes, None))
Command::Trinitrix(Api(Ui(CyclePlanes))),
None,
))
.await?; .await?;
} }
CrosstermEvent::Key(KeyEvent { CrosstermEvent::Key(KeyEvent {
@ -88,10 +77,7 @@ pub async fn handle_normal(app: &mut App<'_>, input_event: &CrosstermEvent) -> R
.. ..
}) => { }) => {
app.tx app.tx
.send(Event::CommandEvent( .send(Event::CommandEvent(Command::CyclePlanesRev, None))
Command::Trinitrix(Api(Ui(CyclePlanesRev))),
None,
))
.await?; .await?;
} }
CrosstermEvent::Key(KeyEvent { CrosstermEvent::Key(KeyEvent {
@ -99,10 +85,7 @@ pub async fn handle_normal(app: &mut App<'_>, input_event: &CrosstermEvent) -> R
.. ..
}) => { }) => {
app.tx app.tx
.send(Event::CommandEvent( .send(Event::CommandEvent(Command::CommandLineShow, None))
Command::Trinitrix(Api(Ui(CommandLineShow))),
None,
))
.await?; .await?;
} }
CrosstermEvent::Key(KeyEvent { CrosstermEvent::Key(KeyEvent {
@ -110,10 +93,7 @@ pub async fn handle_normal(app: &mut App<'_>, input_event: &CrosstermEvent) -> R
.. ..
}) => { }) => {
app.tx app.tx
.send(Event::CommandEvent( .send(Event::CommandEvent(Command::SetModeInsert, None))
Command::Trinitrix(Api(Ui(SetModeInsert))),
None,
))
.await?; .await?;
} }
input => match app.ui.input_position() { input => match app.ui.input_position() {
@ -213,10 +193,7 @@ pub async fn handle_insert(app: &mut App<'_>, event: &CrosstermEvent) -> Result<
code: KeyCode::Esc, .. code: KeyCode::Esc, ..
}) => { }) => {
app.tx app.tx
.send(Event::CommandEvent( .send(Event::CommandEvent(Command::SetModeNormal, None))
Command::Trinitrix(Api(Ui(SetModeNormal))),
None,
))
.await?; .await?;
} }
CrosstermEvent::Key(KeyEvent { CrosstermEvent::Key(KeyEvent {
@ -226,9 +203,7 @@ pub async fn handle_insert(app: &mut App<'_>, event: &CrosstermEvent) -> Result<
}) => { }) => {
app.tx app.tx
.send(Event::CommandEvent( .send(Event::CommandEvent(
Command::Trinitrix(Api(RoomMessageSend( Command::RoomMessageSend(app.ui.message_compose.lines().join("\n")),
app.ui.message_compose.lines().join("\n"),
))),
None, None,
)) ))
.await?; .await?;

View File

@ -3,7 +3,10 @@ use matrix_sdk::deserialized_responses::SyncResponse;
use crate::app::{events::event_types::EventStatus, App}; use crate::app::{events::event_types::EventStatus, App};
pub async fn handle(app: &mut App<'_>, sync: &SyncResponse) -> Result<EventStatus> { pub async fn handle(
app: &mut App<'_>,
sync: &SyncResponse,
) -> Result<EventStatus> {
for (m_room_id, m_room) in sync.rooms.join.iter() { for (m_room_id, m_room) in sync.rooms.join.iter() {
let room = match app.status.get_room_mut(m_room_id) { let room = match app.status.get_room_mut(m_room_id) {
Some(r) => r, Some(r) => r,

View File

@ -6,7 +6,10 @@ use crate::{
ui::setup, ui::setup,
}; };
pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<EventStatus> { pub async fn handle(
app: &mut App<'_>,
input_event: &CrosstermEvent,
) -> Result<EventStatus> {
let ui = match &mut app.ui.setup_ui { let ui = match &mut app.ui.setup_ui {
Some(ui) => ui, Some(ui) => ui,
None => bail!("SetupUI instance not found"), None => bail!("SetupUI instance not found"),

View File

@ -5,14 +5,16 @@ use cli_log::trace;
use crossterm::event::Event as CrosstermEvent; use crossterm::event::Event as CrosstermEvent;
use tokio::sync::oneshot; use tokio::sync::oneshot;
use self::handlers::{command, lua_command, main, matrix, setup};
use super::EventStatus;
use crate::app::{ use crate::app::{
command_interface::{command_transfer_value::CommandTransferValue, Command}, command_interface::{command_transfer_value::CommandTransferValue, Command},
status::State, status::State,
App, App,
}; };
use self::handlers::{command, lua_command, main, matrix, setup};
use super::EventStatus;
#[derive(Debug)] #[derive(Debug)]
pub enum Event { pub enum Event {
InputEvent(CrosstermEvent), InputEvent(CrosstermEvent),

View File

@ -1,4 +1,5 @@
pub mod event; pub mod event;
pub mod event_status; pub mod event_status;
pub use self::{event::*, event_status::*}; pub use self::event::*;
pub use self::event_status::*;

View File

@ -12,7 +12,6 @@ use matrix_sdk::Client;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
use self::{command_interface::lua_command_manager::LuaCommandManager, events::event_types};
use crate::{ use crate::{
accounts::{Account, AccountsManager}, accounts::{Account, AccountsManager},
app::{ app::{
@ -22,6 +21,8 @@ use crate::{
ui::{central, setup}, ui::{central, setup},
}; };
use self::{command_interface::lua_command_manager::LuaCommandManager, events::event_types};
pub struct App<'ui> { pub struct App<'ui> {
ui: central::UI<'ui>, ui: central::UI<'ui>,
accounts_manager: AccountsManager, accounts_manager: AccountsManager,
@ -83,7 +84,10 @@ impl App<'_> {
.context("Failed to search for a config file")? .context("Failed to search for a config file")?
{ {
lua_config_file = Some(config_dir.join("init.lua")); lua_config_file = Some(config_dir.join("init.lua"));
info!("Found config dir: `{}`", config_dir.display()); info!(
"Found config dir: `{}`",
config_dir.display()
);
} else { } else {
warn!("No config dir found!"); warn!("No config dir found!");
} }

View File

@ -156,7 +156,7 @@ impl Status {
state: State::Normal, state: State::Normal,
account_name: "".to_owned(), account_name: "".to_owned(),
account_user_id: "".to_owned(), account_user_id: "".to_owned(),
client, client,
rooms, rooms,
current_room_id: "".to_owned(), current_room_id: "".to_owned(),
status_messages: vec![StatusMessage { status_messages: vec![StatusMessage {

View File

@ -1,7 +1,7 @@
mod accounts;
mod app; mod app;
mod cli;
mod ui; mod ui;
mod accounts;
mod cli;
use clap::Parser; use clap::Parser;

View File

@ -16,10 +16,12 @@ use tui::{
Terminal, Terminal,
}; };
use tui_textarea::TextArea; use tui_textarea::TextArea;
pub use update::*;
use crate::ui::{terminal_prepare, textarea_inactivate, textarea_activate};
use super::setup; use super::setup;
use crate::ui::{terminal_prepare, textarea_activate, textarea_inactivate};
pub use update::*;
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub enum InputPosition { pub enum InputPosition {

View File

@ -1,15 +1,13 @@
use std::cmp; use std::cmp;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use tui::{ use tui::{layout::{Constraint, Direction, Layout}, widgets::{Paragraph, Block, Borders}, style::{Style, Color}};
layout::{Constraint, Direction, Layout},
style::{Color, Style}, use crate::app::status::Status;
widgets::{Block, Borders, Paragraph},
};
use self::widgets::{command_monitor, messages, room_info, rooms, status}; use self::widgets::{command_monitor, messages, room_info, rooms, status};
use super::UI; use super::UI;
use crate::app::status::Status;
pub mod widgets; pub mod widgets;

View File

@ -1,5 +1,5 @@
pub mod command_monitor;
pub mod messages; pub mod messages;
pub mod room_info; pub mod room_info;
pub mod rooms; pub mod rooms;
pub mod status; pub mod status;
pub mod command_monitor;

View File

@ -1,8 +1,7 @@
use tui::{ use tui::{
layout::Alignment,
style::{Color, Style}, style::{Color, Style},
text::Text, text::Text,
widgets::{Block, Borders, Paragraph}, widgets::{Block, Borders, Paragraph}, layout::Alignment,
}; };
use crate::{app::status::Room, ui::central::InputPosition}; use crate::{app::status::Room, ui::central::InputPosition};

View File

@ -1,7 +1,7 @@
use tui::{ use tui::{
style::{Color, Modifier, Style}, style::{Color, Modifier, Style},
text::Span, text::Span,
widgets::{Block, Borders, List, ListItem}, widgets::{Borders, List, ListItem, Block},
}; };
use crate::{app::status::Status, ui::central::InputPosition}; use crate::{app::status::Status, ui::central::InputPosition};

View File

@ -3,7 +3,7 @@ use std::io::Stdout;
use anyhow::Result; use anyhow::Result;
use tui::{ use tui::{
backend::CrosstermBackend, backend::CrosstermBackend,
layout::{Alignment, Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout, Alignment},
style::{Color, Modifier, Style}, style::{Color, Modifier, Style},
text::Span, text::Span,
widgets::{Block, Borders, Paragraph}, widgets::{Block, Borders, Paragraph},