Compare commits
No commits in common. "9139fa277634d148ec7309942cbdf3b464ad1467" and "d7b93178e8fcb47e2499f94a157520806bf7799f" have entirely different histories.
9139fa2776
...
d7b93178e8
|
@ -8,6 +8,3 @@ trinitrix.log
|
||||||
# IDE stuff
|
# IDE stuff
|
||||||
.idea
|
.idea
|
||||||
.direnv
|
.direnv
|
||||||
|
|
||||||
# Lua LS stuff
|
|
||||||
.luarc.json
|
|
||||||
|
|
|
@ -193,9 +193,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.74"
|
version = "0.1.73"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9"
|
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -269,9 +269,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.4.1"
|
version = "2.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blake3"
|
name = "blake3"
|
||||||
|
@ -710,12 +710,6 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "diff"
|
|
||||||
version = "0.1.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -1720,7 +1714,7 @@ version = "0.10.57"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
|
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.1",
|
"bitflags 2.4.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1915,16 +1909,6 @@ version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pretty_assertions"
|
|
||||||
version = "1.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
|
|
||||||
dependencies = [
|
|
||||||
"diff",
|
|
||||||
"yansi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
|
@ -2087,9 +2071,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.10.1"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea"
|
checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -2099,9 +2083,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.2"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d"
|
checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -2110,9 +2094,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.2"
|
version = "0.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
checksum = "56d84fdd47036b038fc80dd333d10b6aab10d5d31f4a366e20014def75328d33"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
|
@ -2269,7 +2253,7 @@ version = "0.38.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed"
|
checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.1",
|
"bitflags 2.4.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
|
@ -2734,7 +2718,6 @@ dependencies = [
|
||||||
"matrix-sdk",
|
"matrix-sdk",
|
||||||
"mlua",
|
"mlua",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pretty_assertions",
|
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -3141,12 +3124,6 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "yansi"
|
|
||||||
version = "0.5.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|
|
@ -31,8 +31,5 @@ serde = { version = "1.0", optional = true }
|
||||||
indexmap = { version = "2.0.2", optional = true }
|
indexmap = { version = "2.0.2", optional = true }
|
||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
pretty_assertions = "1.4.0"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
--- Add a new keymap. This is just a convenience function which registers the function
|
|
||||||
-- and at the same time deals with the fact that the whole trinitrix api is async.
|
|
||||||
---@param mode string
|
|
||||||
---@param key string
|
|
||||||
---@param callback function
|
|
||||||
trinitrix.std.keymaps.add = function(mode, key, callback)
|
|
||||||
local callback_key = trinitrix.api.register_function(function()
|
|
||||||
local co = coroutine.create(callback)
|
|
||||||
while coroutine.status(co) ~= "dead" do
|
|
||||||
coroutine.resume(co)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
trinitrix.api.keymaps.add(mode, key, callback_key)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
trinitrix.std.keymaps.add("ci", "<ESC>", trinitrix.api.ui.set_mode_normal)
|
|
||||||
|
|
||||||
trinitrix.std.keymaps.add("n", ":", trinitrix.api.ui.command_line_show)
|
|
||||||
trinitrix.std.keymaps.add("n", "i", trinitrix.api.ui.set_mode_insert)
|
|
||||||
|
|
||||||
-- a simple test to prove that key chords work
|
|
||||||
trinitrix.std.keymaps.add("n", "jj", function() print("hi") end)
|
|
||||||
|
|
||||||
trinitrix.std.keymaps.add("n", "q", trinitrix.api.exit)
|
|
26
flake.lock
26
flake.lock
|
@ -16,11 +16,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1697408734,
|
"lastModified": 1697165857,
|
||||||
"narHash": "sha256-OVC6KKdeBAfP8NQ+NyatWfuLZNxmrOe7KUB2Qnaes2Y=",
|
"narHash": "sha256-Cho+TPHCIfx/sVo5scjNDP2XUCpUxcyKvoMlJ8w9dgQ=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "d4bddd0a8c7c4b634f85d4fe415ee783ee135fd9",
|
"rev": "117ac48319c0dbcff5540781c7c5b18166e33f6a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -65,11 +65,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1697379843,
|
"lastModified": 1697009197,
|
||||||
"narHash": "sha256-RcnGuJgC2K/UpTy+d32piEoBXq2M+nVFzM3ah/ZdJzg=",
|
"narHash": "sha256-viVRhBTFT8fPJTb1N3brQIpFZnttmwo3JVKNuWRVc3s=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "12bdeb01ff9e2d3917e6a44037ed7df6e6c3df9d",
|
"rev": "01441e14af5e29c9d27ace398e6dd0b293e25a54",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -100,11 +100,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1697422411,
|
"lastModified": 1697249410,
|
||||||
"narHash": "sha256-eCj20wEwATLm7Bd/+/wOIdbqq9jgvS6ZxMrxujX2DxU=",
|
"narHash": "sha256-OmsnxNsjBB1DJlUuJyzDJJ7psbm4/VzokNT+o0ajzFQ=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "056256f2fcf3c5a652dbc3edba9ec1a956d41f56",
|
"rev": "dce60ca7fca201014868c08a612edb73a998310f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -116,11 +116,11 @@
|
||||||
"rustc_cranelift_backend": {
|
"rustc_cranelift_backend": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1697371505,
|
"lastModified": 1696841904,
|
||||||
"narHash": "sha256-5j+psQPh1UB/eDG7qDE1azYhSdFN1F21Usnl3jZroQI=",
|
"narHash": "sha256-/ht3ULqs2lME3E8kFn0hYMH4CQAGV/0NJUpm56oo4RU=",
|
||||||
"owner": "bjorn3",
|
"owner": "bjorn3",
|
||||||
"repo": "rustc_codegen_cranelift",
|
"repo": "rustc_codegen_cranelift",
|
||||||
"rev": "ba0f7e35346b24ceca630889873a1dccc28f8ae9",
|
"rev": "269917bc774ab72ba6d442605751925d3fb801a7",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -133,7 +133,7 @@
|
||||||
"rustc_cranelift_backend_src": {
|
"rustc_cranelift_backend_src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"narHash": "sha256-PWxiQDKsObZn5YgFbPepKN8HXYplYD6GzxiauFKuCyk=",
|
"narHash": "sha256-Nj6LdJfZjxgwN2xSgZ8y0PhWALGcQ1l46I6qhjTsrGQ=",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://github.com/bjorn3/rustc_codegen_cranelift/releases/download/dev/cg_clif-x86_64-unknown-linux-gnu.tar.xz"
|
"url": "https://github.com/bjorn3/rustc_codegen_cranelift/releases/download/dev/cg_clif-x86_64-unknown-linux-gnu.tar.xz"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,9 +4,8 @@ use quote::quote;
|
||||||
use syn::{punctuated::Punctuated, token::Comma, GenericArgument, Lifetime, Token, Type};
|
use syn::{punctuated::Punctuated, token::Comma, GenericArgument, Lifetime, Token, Type};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command_enum_parsing::{Field, FunctionDeclaration, NamespacePath},
|
|
||||||
generate::{get_input_type_of_bare_fn_field, get_return_type_of_bare_fn_field},
|
generate::{get_input_type_of_bare_fn_field, get_return_type_of_bare_fn_field},
|
||||||
DataCommandEnum,
|
DataCommandEnum, command_enum_parsing::{NamespacePath, Field, FunctionDeclaration},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn generate_rust_wrapper_functions(
|
pub fn generate_rust_wrapper_functions(
|
||||||
|
@ -98,17 +97,10 @@ fn get_and_add_lifetimes_form_inputs_and_outputs<'a>(
|
||||||
.collect();
|
.collect();
|
||||||
return Some(lifetime_args);
|
return Some(lifetime_args);
|
||||||
}
|
}
|
||||||
syn::PathArguments::Parenthesized(_) => todo!("Parenthesized Life time"),
|
syn::PathArguments::Parenthesized(_) => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
syn::Type::Tuple(_) => {
|
_ => todo!(),
|
||||||
// TODO(@soispha): I don't really know if tuples can have lifetimes, but let's just
|
|
||||||
// ignore them for now <2023-10-14>
|
|
||||||
dbg!("Ignoring tuple lifetime!");
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
non_path => todo!("Non path lifetime: {:#?}", non_path),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
%s/::core::fmt::Formatter::debug_tuple_field1_finish(f,\(.*\))/println!(\1); return Ok(())
|
|
||||||
|
|
||||||
%s/::alloc::fmt::format(format_args!(\n\s*\(".*"\),\n\s*\(".*"\),\n\s*));/format!(\1,\2);
|
|
||||||
%s/::alloc::fmt::format(format_args!(\n\s*\(".*"\),\n\s*\(".*"\)\n\s*));/format!(\1,\2);
|
|
||||||
%s/::alloc::fmt::format(format_args!(\(".*"\), \(".*"\)));/format!(\1,\2);
|
|
||||||
|
|
||||||
%s/let lvl = ::log::Level::Info;\n\s*if lvl <= ::log::STATIC_MAX_LEVEL && lvl <= ::log::max_level() {\(\n\s*.*\)\{12}//g
|
|
||||||
%s/::log::__private_api::Option::None,\n\s*);\n\s*}/
|
|
|
@ -5,11 +5,9 @@ use language_macros::parse_command_enum;
|
||||||
// TODO(@soispha): Should these paths be moved to the proc macro?
|
// TODO(@soispha): Should these paths be moved to the proc macro?
|
||||||
// As they are not static, it could be easier for other people,
|
// As they are not static, it could be easier for other people,
|
||||||
// if they stay here.
|
// if they stay here.
|
||||||
use crate::app::command_interface::command_transfer_value::{
|
|
||||||
support_types::Function, CommandTransferValue,
|
|
||||||
};
|
|
||||||
use crate::app::Event;
|
|
||||||
use mlua::IntoLua;
|
use mlua::IntoLua;
|
||||||
|
use crate::app::command_interface::command_transfer_value::CommandTransferValue;
|
||||||
|
use crate::app::Event;
|
||||||
|
|
||||||
parse_command_enum! {
|
parse_command_enum! {
|
||||||
commands {
|
commands {
|
||||||
|
@ -20,19 +18,6 @@ commands {
|
||||||
declare print: fn(CommandTransferValue),
|
declare print: fn(CommandTransferValue),
|
||||||
|
|
||||||
namespace trinitrix {
|
namespace trinitrix {
|
||||||
/// Language specific functions, which mirror the `trinitrix.api` namespace.
|
|
||||||
/// That is, if you have to choose between a `std` and a `api` function choose the `std`
|
|
||||||
/// one as it will most likely be more high-level and easier to use (as it isn't abstracted
|
|
||||||
/// over multiple languages). Feel free to drop down to the lower level api, if you feel
|
|
||||||
/// like that more, it should be as stable and user-oriented as the `std` functions
|
|
||||||
namespace std {
|
|
||||||
/// This command is a no-op, it's just here to ensure that the 'std'
|
|
||||||
/// namespace get actually created
|
|
||||||
// FIXME(@soispha): Add an attribute to namespaces to avoid having to use
|
|
||||||
// empty functions <2023-10-14>
|
|
||||||
declare private_initializer_std: fn(),
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Debug only functions, these are effectively useless
|
/// Debug only functions, these are effectively useless
|
||||||
namespace debug {
|
namespace debug {
|
||||||
/// Greets the user
|
/// Greets the user
|
||||||
|
@ -54,10 +39,6 @@ commands {
|
||||||
/// the help pages at the start
|
/// the help pages at the start
|
||||||
declare help: fn(Option<String>),
|
declare help: fn(Option<String>),
|
||||||
|
|
||||||
// Register a function to be used with the Trinitrix api
|
|
||||||
// (This function is not actually implemented here)
|
|
||||||
/* declare register_function: false, */
|
|
||||||
|
|
||||||
/// Function that change the UI, or UI state
|
/// Function that change the UI, or UI state
|
||||||
namespace ui {
|
namespace ui {
|
||||||
/// Shows the command line
|
/// Shows the command line
|
||||||
|
@ -77,72 +58,14 @@ commands {
|
||||||
declare set_mode_insert: fn(),
|
declare set_mode_insert: fn(),
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Manipulate keymappings, the mode is specified as a String build up of all mode
|
/// Functions only used internally within Name
|
||||||
/// the keymapping should be active in. The mapping works as follows:
|
|
||||||
/// n => normal Mode
|
|
||||||
/// c => command Mode
|
|
||||||
/// i => insert Mode
|
|
||||||
///
|
|
||||||
/// The key works in a similar matter, specifying the required keypresses to trigger the
|
|
||||||
/// callback. For example "aba" for require the user to press "a" then "b" then "a" again
|
|
||||||
/// to trigger the mapping. Special characters are encoded as follows:
|
|
||||||
/// "<C-a>ba" => "Ctrl+a" then "b" then "a"
|
|
||||||
/// "<S-a>" => "A" or "Shift+a"
|
|
||||||
/// "A" => "A"
|
|
||||||
/// "<M-a> " => "Alt+a" (<A-a>) or "Meta+a"(<M-a>) (most terminals can't really differentiate between these characters)
|
|
||||||
/// "a<C-b><C-a>" => "a" then "Ctrl+b" then "Ctrl+a" (also works for Shift, Alt and Super)
|
|
||||||
/// "<CSM-b>" => "Ctrl+Shift+Alt+b" (the ordering doesn't matter)
|
|
||||||
/// "a " => "a" then a literal space (" ")
|
|
||||||
/// "å🙂" => "å" then "🙂" (full Unicode support!)
|
|
||||||
/// "<ESC>" => escape key
|
|
||||||
/// "<F3>" => F3 key
|
|
||||||
/// "<BACKSPACE>" => backspace key (and so forth)
|
|
||||||
/// "<DASH>" => a literal "-"
|
|
||||||
/// "<ANGULAR_BRACKET_OPEN>" or "<ABO>" => a literal "<"
|
|
||||||
/// "<ANGULAR_BRACKET_CLOSE>" or "<ABC>" => a literal ">"
|
|
||||||
///
|
|
||||||
/// The callback MUST be registered first by calling
|
|
||||||
/// `trinitrix.api.register_function()` the returned value can than be used to
|
|
||||||
/// set the keymap.
|
|
||||||
namespace keymaps {
|
|
||||||
/// Add a new keymapping
|
|
||||||
declare add: fn((/* mode: */ String, /* key: */ String, /* callback: */ Function)),
|
|
||||||
|
|
||||||
/// Remove a keymapping
|
|
||||||
///
|
|
||||||
/// Does nothing, if the keymapping doesn't exists
|
|
||||||
declare remove: fn((/* mode: */ String, /* key: */ String)),
|
|
||||||
|
|
||||||
/// List declared keymappings
|
|
||||||
declare get: fn(/* mode: */ String),
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Functions only used internally within Trinitrix
|
|
||||||
namespace raw {
|
namespace raw {
|
||||||
/// Send an error to the default error output
|
/// Send an error to the default error output
|
||||||
declare raise_error: fn(String),
|
declare raise_error: fn(String),
|
||||||
|
|
||||||
/// Send output to the default output
|
/// Send output to the default output
|
||||||
/// This is mainly used to display the final
|
/// This is mainly used to display the final
|
||||||
/// output of evaluated lua commands.
|
/// output of evaluated lua commands.
|
||||||
declare display_output: fn(String),
|
declare display_output: fn(String),
|
||||||
|
|
||||||
/// Input a character without checking for possible keymaps
|
|
||||||
/// If the current state does not expect input, this character is ignored
|
|
||||||
/// The encoding is the same as in the `trinitrix.api.keymaps` commands
|
|
||||||
declare send_input_unprocessed: fn(String),
|
|
||||||
|
|
||||||
/// This namespace is used to store some command specific data (like functions, as
|
|
||||||
/// ensuring memory locations stay allocated in garbage collected language is hard)
|
|
||||||
///
|
|
||||||
/// Treat it as an implementation detail
|
|
||||||
namespace __private {
|
|
||||||
/// This command is a no-op, it's just here to ensure that the '__private'
|
|
||||||
/// namespace get actually created
|
|
||||||
// FIXME(@soispha): Add an attribute to namespaces to avoid having to use
|
|
||||||
// empty functions <2023-10-14>
|
|
||||||
declare private_initializer_private: fn(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,22 +3,7 @@ use std::collections::HashMap;
|
||||||
use cli_log::info;
|
use cli_log::info;
|
||||||
use mlua::{ErrorContext, FromLua, IntoLua, LuaSerdeExt, Value};
|
use mlua::{ErrorContext, FromLua, IntoLua, LuaSerdeExt, Value};
|
||||||
|
|
||||||
use super::{support_types::Function, CommandTransferValue, Table};
|
use super::{CommandTransferValue, Table};
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for Function {
|
|
||||||
fn from_lua(value: Value<'lua>, lua: &'lua mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
match value {
|
|
||||||
Value::Integer(function_id) => {
|
|
||||||
return Ok(Function::new(function_id.try_into().expect(
|
|
||||||
"We should never have i64::MAX functions
|
|
||||||
registered. The stack will
|
|
||||||
probably overflow, before this i64 overflows",
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
_ => unreachable!("The Function type can only take functions!"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for CommandTransferValue {
|
impl<'lua> IntoLua<'lua> for CommandTransferValue {
|
||||||
fn into_lua(self, lua: &'lua mlua::Lua) -> mlua::Result<mlua::Value<'lua>> {
|
fn into_lua(self, lua: &'lua mlua::Lua) -> mlua::Result<mlua::Value<'lua>> {
|
||||||
|
|
|
@ -2,9 +2,6 @@ use std::{collections::HashMap, fmt::Display};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use self::support_types::Function;
|
|
||||||
|
|
||||||
pub mod support_types;
|
|
||||||
pub mod type_conversions;
|
pub mod type_conversions;
|
||||||
|
|
||||||
// language support
|
// language support
|
||||||
|
@ -33,10 +30,9 @@ pub enum CommandTransferValue {
|
||||||
|
|
||||||
/// A table, dictionary or HashMap
|
/// A table, dictionary or HashMap
|
||||||
Table(HashMap<String, CommandTransferValue>),
|
Table(HashMap<String, CommandTransferValue>),
|
||||||
|
// Reference to a Lua function (or closure).
|
||||||
|
// /* TODO */ Function(Function),
|
||||||
|
|
||||||
/// Reference to a function (or closure).
|
|
||||||
/// This 'Function' value is obtained by registering a function with 'register_function()'
|
|
||||||
Function(Function),
|
|
||||||
// Reference to a Lua thread (or coroutine).
|
// Reference to a Lua thread (or coroutine).
|
||||||
// /* TODO */ Thread(Thread<'lua>),
|
// /* TODO */ Thread(Thread<'lua>),
|
||||||
|
|
||||||
|
@ -59,7 +55,6 @@ impl Display for CommandTransferValue {
|
||||||
// TODO(@Soispha): The following line should be a real display call, but how do you
|
// TODO(@Soispha): The following line should be a real display call, but how do you
|
||||||
// format a HashMap?
|
// format a HashMap?
|
||||||
CommandTransferValue::Table(table) => f.write_str(&format!("{:#?}", table)),
|
CommandTransferValue::Table(table) => f.write_str(&format!("{:#?}", table)),
|
||||||
CommandTransferValue::Function(function) => function.fmt(f),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
use std::{
|
|
||||||
fmt::Display,
|
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
use cli_log::info;
|
|
||||||
use mlua::{ErrorContext, IntoLua, Lua, Table};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct Function {
|
|
||||||
id: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Function {
|
|
||||||
fn get_private<'lua>(lua: &'lua Lua) -> mlua::Result<Table<'lua>> {
|
|
||||||
let private: Table = lua
|
|
||||||
.globals()
|
|
||||||
// This is always initialized, as the namespaces are specified in the 'command_list' module
|
|
||||||
.get::<&str, mlua::Table>("trinitrix")
|
|
||||||
.context("Failed to access 'trinitrix'")?
|
|
||||||
.get::<&str, mlua::Table>("api")
|
|
||||||
.context("Failed to access 'api'")?
|
|
||||||
.get::<&str, mlua::Table>("raw")
|
|
||||||
.context("Failed to access 'raw'")?
|
|
||||||
.get::<&str, mlua::Table>("__private")
|
|
||||||
.context("Failed to access '__private'")?;
|
|
||||||
Ok(private)
|
|
||||||
}
|
|
||||||
pub fn new(function_id: usize) -> Self {
|
|
||||||
Function { id: function_id }
|
|
||||||
}
|
|
||||||
pub fn from_lua_function(function: mlua::Function, lua: &Lua) -> mlua::Result<Self> {
|
|
||||||
// TODO(@soispha): Does this expose a vulnerability, as the ids are predictable? <2023-10-14>
|
|
||||||
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
|
||||||
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
|
|
||||||
|
|
||||||
let private = Self::get_private(lua)?;
|
|
||||||
info!("Registering function '{}'", id);
|
|
||||||
private.set(id, function)?;
|
|
||||||
|
|
||||||
Ok(Function::new(id))
|
|
||||||
}
|
|
||||||
pub fn call(&self, lua: &Lua) -> mlua::Result<()> {
|
|
||||||
let private = Self::get_private(lua)?;
|
|
||||||
|
|
||||||
info!("Calling function '{}'", &self.id);
|
|
||||||
|
|
||||||
let function: mlua::Function = private
|
|
||||||
.get(self.id)
|
|
||||||
.context("Failed to get function associated with callback!")?;
|
|
||||||
|
|
||||||
function.call(()).context("Failed to call function")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for Function {
|
|
||||||
fn into_lua(self, lua: &'lua Lua) -> mlua::Result<mlua::Value<'lua>> {
|
|
||||||
Ok(self.id.into_lua(lua)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Function {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
self.id.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,11 +2,10 @@ use std::thread;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use cli_log::{debug, error, info};
|
use cli_log::{debug, error, info};
|
||||||
use mlua::{ErrorContext, Lua, Value};
|
use mlua::{Function, Value};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
runtime::Builder,
|
runtime::Builder,
|
||||||
select,
|
|
||||||
sync::{mpsc, Mutex},
|
sync::{mpsc, Mutex},
|
||||||
task::{self, LocalSet},
|
task::{self, LocalSet},
|
||||||
};
|
};
|
||||||
|
@ -22,15 +21,12 @@ use crate::app::{
|
||||||
events::event_types::Event,
|
events::event_types::Event,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::command_transfer_value::support_types::Function;
|
|
||||||
|
|
||||||
static LUA: OnceCell<Mutex<mlua::Lua>> = OnceCell::new();
|
static LUA: OnceCell<Mutex<mlua::Lua>> = OnceCell::new();
|
||||||
|
|
||||||
/// This structure contains the necessary state for running an embedded Lua runtime (i.e.
|
/// This structure contains the necessary state for running an embedded Lua runtime (i.e.
|
||||||
/// the tread, the Lua memory, etc.).
|
/// the tread, the Lua memory, etc.).
|
||||||
pub struct LuaCommandManager {
|
pub struct LuaCommandManager {
|
||||||
lua_command_tx: mpsc::Sender<String>,
|
lua_command_tx: mpsc::Sender<String>,
|
||||||
lua_function_tx: mpsc::Sender<Function>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaCommandManager {
|
impl LuaCommandManager {
|
||||||
|
@ -40,17 +36,10 @@ impl LuaCommandManager {
|
||||||
.await
|
.await
|
||||||
.expect("The receiver should not be dropped at this time");
|
.expect("The receiver should not be dropped at this time");
|
||||||
}
|
}
|
||||||
pub async fn execute_function(&self, function: Function) {
|
|
||||||
self.lua_function_tx
|
|
||||||
.send(function)
|
|
||||||
.await
|
|
||||||
.expect("The receiver should not be dropped");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(event_call_tx: mpsc::Sender<Event>) -> Self {
|
pub fn new(event_call_tx: mpsc::Sender<Event>) -> Self {
|
||||||
info!("Spawning lua code execution thread...");
|
info!("Spawning lua code execution thread...");
|
||||||
let (lua_command_tx, mut lua_command_rx) = mpsc::channel::<String>(256);
|
let (lua_command_tx, mut lua_command_rx) = mpsc::channel::<String>(256);
|
||||||
let (lua_function_tx, mut lua_function_rx) = mpsc::channel::<Function>(256);
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let rt = Builder::new_current_thread().enable_all().build().expect(
|
let rt = Builder::new_current_thread().enable_all().build().expect(
|
||||||
"Should always be able to build \
|
"Should always be able to build \
|
||||||
|
@ -62,14 +51,9 @@ impl LuaCommandManager {
|
||||||
"Lua command handling initialized, \
|
"Lua command handling initialized, \
|
||||||
waiting for commands.."
|
waiting for commands.."
|
||||||
);
|
);
|
||||||
let mut done = false;
|
while let Some(command) = lua_command_rx.recv().await {
|
||||||
let moved_event_call_tx = event_call_tx.clone();
|
debug!("Recieved lua code (in LuaCommandHandler): {}", &command);
|
||||||
while !done {
|
let local_event_call_tx = event_call_tx.clone();
|
||||||
select! {
|
|
||||||
command = lua_command_rx.recv() => {
|
|
||||||
if let Some(command) = command {
|
|
||||||
debug!("Received lua code (in LuaCommandHandler): {}", &command);
|
|
||||||
let local_event_call_tx = moved_event_call_tx.clone();
|
|
||||||
|
|
||||||
task::spawn_local(async move {
|
task::spawn_local(async move {
|
||||||
exec_lua(&command, local_event_call_tx).await.expect(
|
exec_lua(&command, local_event_call_tx).await.expect(
|
||||||
|
@ -78,96 +62,32 @@ impl LuaCommandManager {
|
||||||
this should never error",
|
this should never error",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function_id = lua_function_rx.recv() => {
|
|
||||||
if let Some(function_id) = function_id {
|
|
||||||
debug!("Received lua function (in LuaCommandHandler): {}", &function_id);
|
|
||||||
let local_event_call_tx = moved_event_call_tx.clone();
|
|
||||||
|
|
||||||
task::spawn_local(async move {
|
|
||||||
let lua = initialize_lua(local_event_call_tx.clone()).await;
|
|
||||||
let out = function_id.call(&lua).map_err(|err| async move {
|
|
||||||
error!("Lua function `{}` returned error: `{}`", function_id, err);
|
|
||||||
local_event_call_tx
|
|
||||||
.send(Event::CommandEvent(
|
|
||||||
Command::Trinitrix(Api(Raw(RaiseError(err.to_string())))),
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
.await.expect(
|
|
||||||
"This should return all relevent errors \
|
|
||||||
by other messages, \
|
|
||||||
this should never error",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
if let Err(err) = out {
|
|
||||||
err.await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => done = true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
rt.block_on(local);
|
rt.block_on(local);
|
||||||
});
|
});
|
||||||
|
|
||||||
LuaCommandManager {
|
LuaCommandManager { lua_command_tx }
|
||||||
lua_command_tx,
|
|
||||||
lua_function_tx,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn initialize_lua<'a>(
|
|
||||||
event_call_tx: mpsc::Sender<Event>,
|
|
||||||
) -> matrix_sdk::locks::MutexGuard<'a, Lua> {
|
|
||||||
let lua = LUA
|
|
||||||
.get_or_init(|| {
|
|
||||||
let lua: Lua = add_lua_functions_to_globals(mlua::Lua::new(), event_call_tx);
|
|
||||||
{
|
|
||||||
let wrapped_register_function = lua
|
|
||||||
.create_function(
|
|
||||||
|lua: &Lua, function: mlua::Function| -> mlua::Result<Function> {
|
|
||||||
ErrorContext::context(
|
|
||||||
Function::from_lua_function(function, lua),
|
|
||||||
"Failed to register function",
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.expect("This always works, as the function is static");
|
|
||||||
let trinitrix_api: mlua::Table = lua
|
|
||||||
.globals()
|
|
||||||
.get::<&str, mlua::Table>("trinitrix")
|
|
||||||
.expect("This was set in the add_lua_functions_to_globals function")
|
|
||||||
.get::<&str, mlua::Table>("api")
|
|
||||||
.expect("Same reason");
|
|
||||||
trinitrix_api
|
|
||||||
.set("register_function", wrapped_register_function)
|
|
||||||
.expect("This should work");
|
|
||||||
}
|
|
||||||
|
|
||||||
Mutex::new(lua)
|
|
||||||
})
|
|
||||||
.lock()
|
|
||||||
.await;
|
|
||||||
lua
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exec_lua(lua_code: &str, event_call_tx: mpsc::Sender<Event>) -> Result<()> {
|
async fn exec_lua(lua_code: &str, event_call_tx: mpsc::Sender<Event>) -> Result<()> {
|
||||||
let lua = initialize_lua(event_call_tx.clone()).await;
|
let second_event_call_tx = event_call_tx.clone();
|
||||||
|
let lua = LUA
|
||||||
|
.get_or_init(|| {
|
||||||
|
Mutex::new(add_lua_functions_to_globals(
|
||||||
|
mlua::Lua::new(),
|
||||||
|
second_event_call_tx,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.lock()
|
||||||
|
.await;
|
||||||
|
|
||||||
info!("Recieved code to execute: `{}`, executing...", &lua_code);
|
info!("Recieved code to execute: `{}`, executing...", &lua_code);
|
||||||
let output = lua.load(lua_code).eval_async::<Value>().await;
|
let output = lua.load(lua_code).eval_async::<Value>().await;
|
||||||
match output {
|
match output {
|
||||||
Ok(out) => {
|
Ok(out) => {
|
||||||
let to_string_fn: mlua::Function =
|
let to_string_fn: Function = lua.globals().get("tostring").expect("This always exists");
|
||||||
lua.globals().get("tostring").expect("This always exists");
|
|
||||||
let output: String = to_string_fn.call(out).expect("tostring should not error");
|
let output: String = to_string_fn.call(out).expect("tostring should not error");
|
||||||
info!("Lua code `{}` evaluated to: `{}`", lua_code, &output);
|
info!("Lua code `{}` evaluated to: `{}`", lua_code, &output);
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,16 @@
|
||||||
use std::{collections::HashMap, str::FromStr};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use cli_log::{info, trace, warn};
|
use cli_log::{trace, warn};
|
||||||
use crossterm::event::Event;
|
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
command_interface::{
|
command_interface::{
|
||||||
command_transfer_value::{CommandTransferValue, Table},
|
command_transfer_value::{CommandTransferValue, Table},
|
||||||
Api, Command, Debug, Keymaps, Raw, Trinitrix, Ui,
|
Api, Command, Debug, Raw, Trinitrix, Ui,
|
||||||
},
|
},
|
||||||
events::event_types::EventStatus,
|
events::event_types::EventStatus,
|
||||||
keymappings::{
|
|
||||||
key::{Key, Keys},
|
|
||||||
trie::Node,
|
|
||||||
},
|
|
||||||
status::State,
|
status::State,
|
||||||
App,
|
App,
|
||||||
},
|
},
|
||||||
|
@ -37,18 +32,18 @@ pub async fn handle(
|
||||||
// is reserved for functions called only for their output (for example `greet()`).
|
// is reserved for functions called only for their output (for example `greet()`).
|
||||||
macro_rules! send_status_output {
|
macro_rules! send_status_output {
|
||||||
($str:expr) => {
|
($str:expr) => {
|
||||||
app.status.add_status_message($str.to_owned())
|
app.status.add_status_message($str.to_owned());
|
||||||
};
|
};
|
||||||
($str:expr, $($args:ident),+) => {
|
($str:expr, $($args:ident),+) => {
|
||||||
app.status.add_status_message(format!($str, $($args),+))
|
app.status.add_status_message(format!($str, $($args),+));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
macro_rules! send_error_output {
|
macro_rules! send_error_output {
|
||||||
($str:expr) => {
|
($str:expr) => {
|
||||||
app.status.add_error_message($str.to_owned())
|
app.status.add_error_message($str.to_owned());
|
||||||
};
|
};
|
||||||
($str:expr, $($args:ident),+) => {
|
($str:expr, $($args:ident),+) => {
|
||||||
app.status.add_error_message(format!($str, $($args),+))
|
app.status.add_error_message(format!($str, $($args),+));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
macro_rules! send_main_output {
|
macro_rules! send_main_output {
|
||||||
|
@ -112,7 +107,7 @@ pub async fn handle(
|
||||||
room.send(msg.clone()).await?;
|
room.send(msg.clone()).await?;
|
||||||
send_status_output!("Sent message: `{}`", msg);
|
send_status_output!("Sent message: `{}`", msg);
|
||||||
} else {
|
} else {
|
||||||
// FIXME(@soispha): This should raise an error within lua, as it would
|
// // FIXME(@soispha): This should raise an error within lua, as it would
|
||||||
// otherwise be very confusing <2023-09-20>
|
// otherwise be very confusing <2023-09-20>
|
||||||
warn!("Can't send message: `{}`, as there is no open room!", &msg);
|
warn!("Can't send message: `{}`, as there is no open room!", &msg);
|
||||||
}
|
}
|
||||||
|
@ -153,46 +148,6 @@ pub async fn handle(
|
||||||
EventStatus::Ok
|
EventStatus::Ok
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Api::Keymaps(keymaps) => match keymaps {
|
|
||||||
Keymaps::Add((mode, key, callback)) => {
|
|
||||||
mode.chars().for_each(|char| {
|
|
||||||
info!("Setting keymaping ('{}') for mode '{}'", key, char);
|
|
||||||
let parsed_keys = key
|
|
||||||
.parse::<Keys>()
|
|
||||||
.map_err(|err| {
|
|
||||||
send_error_output!(err.to_string());
|
|
||||||
})
|
|
||||||
.expect("We dealt with the error");
|
|
||||||
|
|
||||||
match State::from_char(&char) {
|
|
||||||
Ok(state) => {
|
|
||||||
info!("Set for state '{}'", state);
|
|
||||||
let trie;
|
|
||||||
if let Some(collected_trie) = app.key_mappings.get_mut(&state) {
|
|
||||||
trie = collected_trie;
|
|
||||||
} else {
|
|
||||||
app.key_mappings.insert(state.clone(), Node::new());
|
|
||||||
trie = app
|
|
||||||
.key_mappings
|
|
||||||
.get_mut(&state)
|
|
||||||
.expect("Should be set");
|
|
||||||
}
|
|
||||||
trie.insert(&parsed_keys, callback.to_owned())
|
|
||||||
.map_err(|err| {
|
|
||||||
send_error_output!(err.to_string());
|
|
||||||
})
|
|
||||||
.expect("We already dealt with the error")
|
|
||||||
}
|
|
||||||
Err(err) => send_error_output!(err.to_string()),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
EventStatus::Ok
|
|
||||||
}
|
|
||||||
// TODO(@soispha): Well.., we should probably add these functions: <2023-10-15>
|
|
||||||
Keymaps::Remove((mode, key)) => todo!(),
|
|
||||||
Keymaps::Get(mode) => todo!(),
|
|
||||||
},
|
|
||||||
Api::Raw(raw) => match raw {
|
Api::Raw(raw) => match raw {
|
||||||
Raw::RaiseError(err) => {
|
Raw::RaiseError(err) => {
|
||||||
send_error_output!(err);
|
send_error_output!(err);
|
||||||
|
@ -205,42 +160,8 @@ pub async fn handle(
|
||||||
send_status_output!(output);
|
send_status_output!(output);
|
||||||
EventStatus::Ok
|
EventStatus::Ok
|
||||||
}
|
}
|
||||||
Raw::Private(_) => {
|
|
||||||
// no-op, read the comment about it in the `command_list`
|
|
||||||
EventStatus::Ok
|
|
||||||
}
|
|
||||||
Raw::SendInputUnprocessed(char) => match app.status.state() {
|
|
||||||
State::Insert => {
|
|
||||||
let key = Key::from_str(char)?;
|
|
||||||
let cross_input: Event = key.try_into()?;
|
|
||||||
app.ui
|
|
||||||
.message_compose
|
|
||||||
.input(tui_textarea::Input::from(cross_input));
|
|
||||||
EventStatus::Ok
|
|
||||||
}
|
|
||||||
State::Command => {
|
|
||||||
let key = Key::from_str(char)?;
|
|
||||||
let cross_input: Event = key.try_into()?;
|
|
||||||
app.ui
|
|
||||||
.cli
|
|
||||||
.as_mut()
|
|
||||||
.expect("This should exist, when the state is 'Command'")
|
|
||||||
.input(tui_textarea::Input::from(cross_input));
|
|
||||||
EventStatus::Ok
|
|
||||||
}
|
|
||||||
State::Normal
|
|
||||||
| State::Setup
|
|
||||||
| State::KeyInputPending {
|
|
||||||
old_state: _,
|
|
||||||
pending_keys: _,
|
|
||||||
} => EventStatus::Ok,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Trinitrix::Std(_) => {
|
|
||||||
// no-op, read the comment about it in the `command_list`
|
|
||||||
EventStatus::Ok
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
use crate::app::{events::event_types::EventStatus, App, command_interface::command_transfer_value::support_types::Function};
|
|
||||||
|
|
||||||
// TODO(@soispha): We just assume for now that all functions originate in lua. This module will in
|
|
||||||
// future versions house check for the language the function came from <2023-10-15>
|
|
||||||
pub async fn handle(app: &mut App<'_>, function: Function) -> Result<EventStatus> {
|
|
||||||
app.lua.execute_function(function).await;
|
|
||||||
|
|
||||||
Ok(EventStatus::Ok)
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use cli_log::info;
|
|
||||||
use crossterm::event::Event as CrosstermEvent;
|
|
||||||
|
|
||||||
use crate::app::{
|
|
||||||
command_interface::{Api::Raw, Command, Raw::SendInputUnprocessed, Trinitrix::Api},
|
|
||||||
events::event_types::{Event, EventStatus},
|
|
||||||
keymappings::key::{Key, Keys},
|
|
||||||
status::State,
|
|
||||||
App,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<EventStatus> {
|
|
||||||
async fn default(converted_key: Key, app: &mut App<'_>, old_state: &State) -> Result<()> {
|
|
||||||
// Just let the input event slip through if no keymap matches
|
|
||||||
app.tx
|
|
||||||
.send(Event::CommandEvent(
|
|
||||||
Command::Trinitrix(Api(Raw(SendInputUnprocessed(
|
|
||||||
converted_key.to_string_repr(),
|
|
||||||
)))),
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
.await?;
|
|
||||||
app.status.set_state(old_state.to_owned());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
if let CrosstermEvent::Key(_) = input_event {
|
|
||||||
// r
|
|
||||||
// |
|
|
||||||
// a
|
|
||||||
// / \
|
|
||||||
// b a
|
|
||||||
// |
|
|
||||||
// c
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// r->a->a: Some([a]) -> a.is_child() && a.is_terminal() ? call a : *key input pending*
|
|
||||||
// r->a->b: Some([b]) -> b.is_child() && b.is_terminal() ? *call b* : key input pending
|
|
||||||
// r->a->c: None -> continue
|
|
||||||
// r->a->a->c: Some([c]) -> c.is_child() && c.is_terminal() ? *call c* : key input pending
|
|
||||||
//
|
|
||||||
// r->a: Some([a, b]) -> key input pending
|
|
||||||
|
|
||||||
let converted_key: Key = input_event.try_into()?;
|
|
||||||
info!("Received input to handle: '{}'", converted_key);
|
|
||||||
let mut converted_keys: Keys = Keys::new(converted_key);
|
|
||||||
|
|
||||||
let mut old_state = app.status.state().clone();
|
|
||||||
if let State::KeyInputPending {
|
|
||||||
old_state: old,
|
|
||||||
pending_keys,
|
|
||||||
} = app.status.state().clone()
|
|
||||||
{
|
|
||||||
info!("Found KeyInputPending mode!");
|
|
||||||
old_state = *old;
|
|
||||||
converted_keys = pending_keys.join(converted_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(key_maps) = app.key_mappings.get(&old_state) {
|
|
||||||
if let Some((possible_key_maps, should_call)) = key_maps.get(&converted_keys) {
|
|
||||||
info!("possible key maps: {:#?}", possible_key_maps);
|
|
||||||
|
|
||||||
if possible_key_maps.len() == 1 {
|
|
||||||
let possible_key_map = possible_key_maps.get(0).expect("The len is 1");
|
|
||||||
|
|
||||||
if possible_key_map.is_child() && possible_key_map.is_terminal() && should_call {
|
|
||||||
let function = possible_key_map
|
|
||||||
.value()
|
|
||||||
.expect("This node is terminal and a child, it should have a value");
|
|
||||||
app.tx.send(Event::Function(*function)).await?;
|
|
||||||
app.status.set_state(old_state.to_owned());
|
|
||||||
} else {
|
|
||||||
// The choice does not have a value attached to it (might be a waypoint)
|
|
||||||
app.status.set_state(State::KeyInputPending {
|
|
||||||
old_state: Box::new(old_state.to_owned()),
|
|
||||||
pending_keys: converted_keys,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
app.status.set_state(State::KeyInputPending {
|
|
||||||
old_state: Box::new(old_state.to_owned()),
|
|
||||||
pending_keys: converted_keys,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
default(converted_key, app, &old_state).await?
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
default(converted_key, app, &old_state).await?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(EventStatus::Ok)
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
// input events
|
// input events
|
||||||
pub mod input;
|
pub mod main;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
|
|
||||||
// matrix
|
// matrix
|
||||||
|
@ -8,4 +8,3 @@ pub mod matrix;
|
||||||
// ci
|
// ci
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod lua_command;
|
pub mod lua_command;
|
||||||
pub mod function;
|
|
||||||
|
|
|
@ -5,11 +5,12 @@ 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 super::EventStatus;
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
command_interface::{command_transfer_value::{CommandTransferValue, support_types::Function}, Command},
|
command_interface::{command_transfer_value::CommandTransferValue, Command},
|
||||||
status::State,
|
status::State,
|
||||||
App, events::event_types::event::handlers::{input, matrix, command, lua_command, setup, function},
|
App,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -18,7 +19,6 @@ pub enum Event {
|
||||||
MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse),
|
MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse),
|
||||||
CommandEvent(Command, Option<oneshot::Sender<CommandTransferValue>>),
|
CommandEvent(Command, Option<oneshot::Sender<CommandTransferValue>>),
|
||||||
LuaCommand(String),
|
LuaCommand(String),
|
||||||
Function(Function),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
|
@ -32,21 +32,23 @@ impl Event {
|
||||||
Event::CommandEvent(event, callback_tx) => command::handle(app, &event, callback_tx)
|
Event::CommandEvent(event, callback_tx) => command::handle(app, &event, callback_tx)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to handle command event: `{:#?}`", event)),
|
.with_context(|| format!("Failed to handle command event: `{:#?}`", event)),
|
||||||
|
|
||||||
Event::LuaCommand(lua_code) => lua_command::handle(app, lua_code.to_owned())
|
Event::LuaCommand(lua_code) => lua_command::handle(app, lua_code.to_owned())
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to handle lua code: `{}`", lua_code)),
|
.with_context(|| format!("Failed to handle lua code: `{}`", lua_code)),
|
||||||
Event::Function(function) => function::handle(app, function.to_owned())
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("Failed to handle function: `{}`", function)),
|
|
||||||
|
|
||||||
Event::InputEvent(event) => match app.status.state() {
|
Event::InputEvent(event) => match app.status.state() {
|
||||||
|
State::Normal => main::handle_normal(app, &event).await.with_context(|| {
|
||||||
|
format!("Failed to handle input (normal) event: `{:#?}`", event)
|
||||||
|
}),
|
||||||
|
State::Insert => main::handle_insert(app, &event).await.with_context(|| {
|
||||||
|
format!("Failed to handle input (insert) event: `{:#?}`", event)
|
||||||
|
}),
|
||||||
|
State::Command => main::handle_command(app, &event).await.with_context(|| {
|
||||||
|
format!("Failed to handle input (command) event: `{:#?}`", event)
|
||||||
|
}),
|
||||||
State::Setup => setup::handle(app, &event).await.with_context(|| {
|
State::Setup => setup::handle(app, &event).await.with_context(|| {
|
||||||
format!("Failed to handle input (setup) event: `{:#?}`", event)
|
format!("Failed to handle input (setup) event: `{:#?}`", event)
|
||||||
}),
|
}),
|
||||||
_ => input::handle(app, &event).await.with_context(|| {
|
|
||||||
format!("Failed to handle input (non-setup) event: `{:#?}`", event)
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
use std::{collections::VecDeque, fmt::Display};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(super) struct Chars(pub(super) VecDeque<char>);
|
|
||||||
impl Chars {
|
|
||||||
pub(super) fn peek(&self) -> Option<&char> {
|
|
||||||
self.0.front()
|
|
||||||
}
|
|
||||||
pub(super) fn pop(&mut self) -> Option<char> {
|
|
||||||
self.0.pop_front()
|
|
||||||
}
|
|
||||||
pub(super) fn prepend(&mut self, char_to_prepend: char) {
|
|
||||||
let mut new_vec = VecDeque::with_capacity(self.0.len() + 1);
|
|
||||||
new_vec.push_back(char_to_prepend);
|
|
||||||
new_vec.append(&mut self.0);
|
|
||||||
|
|
||||||
self.0 = new_vec;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Chars {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(&self.0.iter().collect::<String>()[..])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,297 +0,0 @@
|
||||||
use std::{str::FromStr, fmt::Display};
|
|
||||||
|
|
||||||
use anyhow::{bail, Context};
|
|
||||||
use crossterm::event::{Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
|
|
||||||
|
|
||||||
use super::{Chars, KeyValue, Keys};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
|
||||||
pub struct Key {
|
|
||||||
// Modifiers
|
|
||||||
pub(super) alt: bool,
|
|
||||||
pub(super) ctrl: bool,
|
|
||||||
pub(super) meta: bool,
|
|
||||||
pub(super) shift: bool,
|
|
||||||
|
|
||||||
pub(super) value: Option<KeyValue>,
|
|
||||||
}
|
|
||||||
impl Key {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Key {
|
|
||||||
alt: false,
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
value: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn value(&self) -> Option<&KeyValue> {
|
|
||||||
self.value.as_ref()
|
|
||||||
}
|
|
||||||
pub fn to_string_repr(self) -> String {
|
|
||||||
let mut output = String::new();
|
|
||||||
if self.alt || self.ctrl || self.meta || self.shift {
|
|
||||||
output.push('<')
|
|
||||||
}
|
|
||||||
if self.alt {
|
|
||||||
output.push('A');
|
|
||||||
}
|
|
||||||
if self.ctrl {
|
|
||||||
output.push('C');
|
|
||||||
}
|
|
||||||
if self.meta {
|
|
||||||
output.push('M');
|
|
||||||
}
|
|
||||||
if self.shift {
|
|
||||||
output.push('S');
|
|
||||||
}
|
|
||||||
if self.alt || self.ctrl || self.meta || self.shift {
|
|
||||||
output.push('-')
|
|
||||||
}
|
|
||||||
output.push_str(
|
|
||||||
&self
|
|
||||||
.value
|
|
||||||
.expect("There can be no Nones here, if the Key comes from the public api")
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
if self.alt || self.ctrl || self.meta || self.shift {
|
|
||||||
output.push('>')
|
|
||||||
}
|
|
||||||
output
|
|
||||||
}
|
|
||||||
fn merge_with(mut self, other: Key) -> Self {
|
|
||||||
// Modifiers
|
|
||||||
self.alt = self.alt || other.alt;
|
|
||||||
self.ctrl = self.ctrl || other.ctrl;
|
|
||||||
self.meta = self.meta || other.meta;
|
|
||||||
self.shift = self.shift || other.shift;
|
|
||||||
|
|
||||||
self.value = Some(self.value.unwrap_or(other.value.unwrap_or(KeyValue::Null)));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub(super) fn parse(chars: &mut Chars) -> anyhow::Result<Self> {
|
|
||||||
assert_eq!(chars.pop().expect("This is a developer error"), '<');
|
|
||||||
let mut parse_buffer: Vec<char> = Vec::new();
|
|
||||||
|
|
||||||
let mut reached_non_modifier = false;
|
|
||||||
let mut output_key_filled = false;
|
|
||||||
let mut output_key = Key::new();
|
|
||||||
while let Some(char) = chars.pop() {
|
|
||||||
if char == '>' {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
if char.is_ascii_uppercase()
|
|
||||||
|| char.is_numeric() && !reached_non_modifier && !output_key_filled
|
|
||||||
{
|
|
||||||
parse_buffer.push(char);
|
|
||||||
} else if char == '-' && !reached_non_modifier && !output_key_filled {
|
|
||||||
// We moved to the modified char
|
|
||||||
reached_non_modifier = true;
|
|
||||||
|
|
||||||
// Our parse_buffer should only contain modifiers:
|
|
||||||
let mut alt = false;
|
|
||||||
let mut ctrl = false;
|
|
||||||
let mut meta = false;
|
|
||||||
let mut shift = false;
|
|
||||||
|
|
||||||
for char in &parse_buffer {
|
|
||||||
match char {
|
|
||||||
'A' => alt = true,
|
|
||||||
'C' => ctrl = true,
|
|
||||||
'M' => meta = true,
|
|
||||||
'S' => shift = true,
|
|
||||||
char => bail!(
|
|
||||||
"The char ('{}') is not a valid descriptor of a modifier",
|
|
||||||
char
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output_key = Key {
|
|
||||||
alt,
|
|
||||||
ctrl,
|
|
||||||
meta,
|
|
||||||
shift,
|
|
||||||
value: None,
|
|
||||||
};
|
|
||||||
} else if reached_non_modifier && !output_key_filled {
|
|
||||||
if char == '<' {
|
|
||||||
chars.prepend('<');
|
|
||||||
let key = Key::parse(chars)?;
|
|
||||||
output_key = output_key.merge_with(key);
|
|
||||||
} else {
|
|
||||||
output_key.value = Some(KeyValue::Char(char));
|
|
||||||
}
|
|
||||||
output_key_filled = true;
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
"Your can not put a this char here!
|
|
||||||
parse_buffer: '{}';
|
|
||||||
char: '{}';
|
|
||||||
chars: '{:#?}';
|
|
||||||
output_key: '{:#?}' ",
|
|
||||||
&parse_buffer.iter().collect::<String>(),
|
|
||||||
&char,
|
|
||||||
&chars,
|
|
||||||
&output_key
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if output_key_filled {
|
|
||||||
Ok(output_key)
|
|
||||||
} else {
|
|
||||||
let mut parse_buffer = Chars(parse_buffer.into());
|
|
||||||
let get_output = |value: KeyValue| -> Key {
|
|
||||||
let key = Key {
|
|
||||||
alt: false,
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
value: Some(value),
|
|
||||||
};
|
|
||||||
|
|
||||||
return key.merge_with(output_key);
|
|
||||||
};
|
|
||||||
if let Some(char) = parse_buffer.peek() {
|
|
||||||
if char == &'F' {
|
|
||||||
let _f = parse_buffer.pop();
|
|
||||||
let number: u8 = parse_buffer.to_string().parse().with_context(|| {
|
|
||||||
format!("Failed to parse buffer ('{}') as u8", &parse_buffer)
|
|
||||||
})?;
|
|
||||||
Ok(get_output(KeyValue::F(number)))
|
|
||||||
} else {
|
|
||||||
match &parse_buffer.to_string()[..] {
|
|
||||||
"BACKSPACE" => Ok(get_output(KeyValue::Backspace)),
|
|
||||||
"ENTER" => Ok(get_output(KeyValue::Enter)),
|
|
||||||
"LEFT" => Ok(get_output(KeyValue::Left)),
|
|
||||||
"RIGHT" => Ok(get_output(KeyValue::Right)),
|
|
||||||
"UP" => Ok(get_output(KeyValue::Up)),
|
|
||||||
"DOWN" => Ok(get_output(KeyValue::Down)),
|
|
||||||
"HOME" => Ok(get_output(KeyValue::Home)),
|
|
||||||
"END" => Ok(get_output(KeyValue::End)),
|
|
||||||
"PAGEUP" => Ok(get_output(KeyValue::PageUp)),
|
|
||||||
"PAGEDOWN" => Ok(get_output(KeyValue::PageDown)),
|
|
||||||
"TAB" => Ok(get_output(KeyValue::Tab)),
|
|
||||||
"BACKTAB" => Ok(get_output(KeyValue::BackTab)),
|
|
||||||
"DELETE" => Ok(get_output(KeyValue::Delete)),
|
|
||||||
"INSERT" => Ok(get_output(KeyValue::Insert)),
|
|
||||||
"ESC" => Ok(get_output(KeyValue::Esc)),
|
|
||||||
"CAPSLOCK" => Ok(get_output(KeyValue::CapsLock)),
|
|
||||||
"SCROLLlOCK" => Ok(get_output(KeyValue::ScrollLock)),
|
|
||||||
"NUMLOCK" => Ok(get_output(KeyValue::NumLock)),
|
|
||||||
"PRINTSCREEN" => Ok(get_output(KeyValue::PrintScreen)),
|
|
||||||
"PAUSE" => Ok(get_output(KeyValue::Pause)),
|
|
||||||
"MENU" => Ok(get_output(KeyValue::Menu)),
|
|
||||||
"KEYPADBEGIN" => Ok(get_output(KeyValue::KeypadBegin)),
|
|
||||||
|
|
||||||
"DASH" => Ok(get_output(KeyValue::Char('-'))),
|
|
||||||
"ANGULAR_BRACKET_OPEN" | "ABO" => Ok(get_output(KeyValue::Char('<'))),
|
|
||||||
"ANGULAR_BRACKET_CLOSE" | "ABC" => Ok(get_output(KeyValue::Char('>'))),
|
|
||||||
other_str => bail!(
|
|
||||||
"The String ('{}') is not a correct special key name!",
|
|
||||||
other_str
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
"You need to put something into the angulare brackets (<>)
|
|
||||||
parse_buffer: '{}';
|
|
||||||
chars: '{:#?}';",
|
|
||||||
&parse_buffer,
|
|
||||||
&chars,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Display for Key {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(&self.to_string_repr())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Into<Event> for Key {
|
|
||||||
fn into(self) -> Event {
|
|
||||||
let mut modifiers;
|
|
||||||
{
|
|
||||||
modifiers = KeyModifiers::all();
|
|
||||||
if !self.alt {
|
|
||||||
modifiers.remove(KeyModifiers::ALT);
|
|
||||||
}
|
|
||||||
if !self.ctrl {
|
|
||||||
modifiers.remove(KeyModifiers::CONTROL);
|
|
||||||
}
|
|
||||||
if !self.meta {
|
|
||||||
modifiers.remove(KeyModifiers::META);
|
|
||||||
modifiers.remove(KeyModifiers::SUPER);
|
|
||||||
}
|
|
||||||
if !self.shift {
|
|
||||||
modifiers.remove(KeyModifiers::SHIFT);
|
|
||||||
}
|
|
||||||
modifiers.remove(KeyModifiers::HYPER);
|
|
||||||
modifiers.remove(KeyModifiers::META);
|
|
||||||
if self.alt || self.ctrl || self.meta || self.shift {
|
|
||||||
modifiers.remove(KeyModifiers::NONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let output = Event::Key(KeyEvent {
|
|
||||||
code: self.value.unwrap_or(KeyValue::Null).into(),
|
|
||||||
modifiers,
|
|
||||||
kind: KeyEventKind::Press,
|
|
||||||
state: KeyEventState::NONE,
|
|
||||||
});
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&Event> for Key {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(value: &Event) -> std::result::Result<Self, Self::Error> {
|
|
||||||
let mut output_key: Key = Key::new();
|
|
||||||
match value {
|
|
||||||
Event::Key(key_event) => {
|
|
||||||
{
|
|
||||||
let key_mods = key_event.modifiers;
|
|
||||||
|
|
||||||
output_key.alt = KeyModifiers::intersects(&key_mods, KeyModifiers::ALT);
|
|
||||||
output_key.ctrl = KeyModifiers::intersects(&key_mods, KeyModifiers::CONTROL);
|
|
||||||
output_key.meta = KeyModifiers::intersects(&key_mods, KeyModifiers::META)
|
|
||||||
|| KeyModifiers::intersects(&key_mods, KeyModifiers::SUPER);
|
|
||||||
output_key.shift = KeyModifiers::intersects(&key_mods, KeyModifiers::SHIFT);
|
|
||||||
// let hyper = KeyModifiers::intersects(&key_mods, KeyModifiers::HYPER);
|
|
||||||
// let none = KeyModifiers::intersects(&key_mods, KeyModifiers::NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let key_code = key_event.code;
|
|
||||||
output_key.value = Some(key_code.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(output_key)
|
|
||||||
}
|
|
||||||
Event::Mouse(_)
|
|
||||||
| Event::Paste(_)
|
|
||||||
| Event::Resize(_, _)
|
|
||||||
| Event::FocusGained
|
|
||||||
| Event::FocusLost => bail!("Only supports parsing from key event"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromStr for Key {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let mut keys: Keys = s.parse().context("Failed to parse string as keys")?;
|
|
||||||
if keys.0.len() == 1 {
|
|
||||||
let key = keys
|
|
||||||
.0
|
|
||||||
.pop()
|
|
||||||
.expect("The vec should have exactly one element");
|
|
||||||
return Ok(key);
|
|
||||||
} else {
|
|
||||||
bail!("The string ('{}') contains more than one key", &s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
use std::fmt::{Display, Write};
|
|
||||||
|
|
||||||
use crossterm::event::KeyCode;
|
|
||||||
|
|
||||||
// taken directly from crossterm
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
|
||||||
pub enum KeyValue {
|
|
||||||
Backspace,
|
|
||||||
Enter,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Up,
|
|
||||||
Down,
|
|
||||||
Home,
|
|
||||||
End,
|
|
||||||
PageUp,
|
|
||||||
PageDown,
|
|
||||||
Tab,
|
|
||||||
BackTab,
|
|
||||||
Delete,
|
|
||||||
Insert,
|
|
||||||
F(u8),
|
|
||||||
Char(char),
|
|
||||||
Null, // TODO(@soispha): What is this key? <2023-10-15>
|
|
||||||
Esc,
|
|
||||||
CapsLock,
|
|
||||||
ScrollLock,
|
|
||||||
NumLock,
|
|
||||||
PrintScreen,
|
|
||||||
Pause,
|
|
||||||
Menu,
|
|
||||||
KeypadBegin,
|
|
||||||
// TODO(@soispha): We could add support for these: <2023-10-15>
|
|
||||||
// Media(MediaKeyCode),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for KeyValue {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let mut w = |str| return f.write_str(str);
|
|
||||||
match self {
|
|
||||||
KeyValue::Backspace => w("<BACKSPACE>"),
|
|
||||||
KeyValue::Enter => w("<ENTER>"),
|
|
||||||
KeyValue::Left => w("<LEFT>"),
|
|
||||||
KeyValue::Right => w("<RIGHT>"),
|
|
||||||
KeyValue::Up => w("<UP>"),
|
|
||||||
KeyValue::Down => w("<DOWN>"),
|
|
||||||
KeyValue::Home => w("<HOME>"),
|
|
||||||
KeyValue::End => w("<END>"),
|
|
||||||
KeyValue::PageUp => w("<PAGEUP>"),
|
|
||||||
KeyValue::PageDown => w("<PAGEDOWN>"),
|
|
||||||
KeyValue::Tab => w("<TAB>"),
|
|
||||||
KeyValue::BackTab => w("<BACKTAB>"),
|
|
||||||
KeyValue::Delete => w("<DELETE>"),
|
|
||||||
KeyValue::Insert => w("<INSERT>"),
|
|
||||||
KeyValue::F(n) => f.write_fmt(format_args!("<F{}>", n)),
|
|
||||||
KeyValue::Char(c) => {
|
|
||||||
match c {
|
|
||||||
'<' => w("<ANGULAR_BRACKET_OPEN>"),
|
|
||||||
'>' => w("<ANGULAR_BRACKET_CLOSE>"),
|
|
||||||
'-' => w("<DASH>"),
|
|
||||||
c => f.write_char(*c),
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
KeyValue::Null => w("<NULL>"),
|
|
||||||
KeyValue::Esc => w("<ESC>"),
|
|
||||||
KeyValue::CapsLock => w("<CAPSLOCK>"),
|
|
||||||
KeyValue::ScrollLock => w("<SCROLLLOCK>"),
|
|
||||||
KeyValue::NumLock => w("<NUMLOCK>"),
|
|
||||||
KeyValue::PrintScreen => w("<PRINTSCREEN>"),
|
|
||||||
KeyValue::Pause => w("<PAUSE>"),
|
|
||||||
KeyValue::Menu => w("<MENU>"),
|
|
||||||
KeyValue::KeypadBegin => w("<KEYPADBEGIN>"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<KeyCode> for KeyValue {
|
|
||||||
fn from(value: KeyCode) -> Self {
|
|
||||||
match value {
|
|
||||||
KeyCode::Backspace => Self::Backspace,
|
|
||||||
KeyCode::Enter => Self::Enter,
|
|
||||||
KeyCode::Left => Self::Left,
|
|
||||||
KeyCode::Right => Self::Right,
|
|
||||||
KeyCode::Up => Self::Up,
|
|
||||||
KeyCode::Down => Self::Down,
|
|
||||||
KeyCode::Home => Self::Home,
|
|
||||||
KeyCode::End => Self::End,
|
|
||||||
KeyCode::PageUp => Self::PageUp,
|
|
||||||
KeyCode::PageDown => Self::PageDown,
|
|
||||||
KeyCode::Tab => Self::Tab,
|
|
||||||
KeyCode::BackTab => Self::BackTab,
|
|
||||||
KeyCode::Delete => Self::Delete,
|
|
||||||
KeyCode::Insert => Self::Insert,
|
|
||||||
KeyCode::F(n) => Self::F(n),
|
|
||||||
KeyCode::Char(c) => Self::Char(c),
|
|
||||||
KeyCode::Null => Self::Null,
|
|
||||||
KeyCode::Esc => Self::Esc,
|
|
||||||
KeyCode::CapsLock => Self::CapsLock,
|
|
||||||
KeyCode::ScrollLock => Self::ScrollLock,
|
|
||||||
KeyCode::NumLock => Self::NumLock,
|
|
||||||
KeyCode::PrintScreen => Self::PrintScreen,
|
|
||||||
KeyCode::Pause => Self::Pause,
|
|
||||||
KeyCode::Menu => Self::Menu,
|
|
||||||
KeyCode::KeypadBegin => Self::KeypadBegin,
|
|
||||||
// FIXME(@soispha): This reduces our information, casting a KeyCode to a KeyValue
|
|
||||||
// and back again would not equal the original KeyCode <2023-10-15>
|
|
||||||
KeyCode::Media(_) => Self::Null,
|
|
||||||
KeyCode::Modifier(_) => Self::Null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Into<KeyCode> for KeyValue {
|
|
||||||
fn into(self) -> KeyCode {
|
|
||||||
match self {
|
|
||||||
Self::Backspace => KeyCode::Backspace,
|
|
||||||
Self::Enter => KeyCode::Enter,
|
|
||||||
Self::Left => KeyCode::Left,
|
|
||||||
Self::Right => KeyCode::Right,
|
|
||||||
Self::Up => KeyCode::Up,
|
|
||||||
Self::Down => KeyCode::Down,
|
|
||||||
Self::Home => KeyCode::Home,
|
|
||||||
Self::End => KeyCode::End,
|
|
||||||
Self::PageUp => KeyCode::PageUp,
|
|
||||||
Self::PageDown => KeyCode::PageDown,
|
|
||||||
Self::Tab => KeyCode::Tab,
|
|
||||||
Self::BackTab => KeyCode::BackTab,
|
|
||||||
Self::Delete => KeyCode::Delete,
|
|
||||||
Self::Insert => KeyCode::Insert,
|
|
||||||
Self::F(n) => KeyCode::F(n),
|
|
||||||
Self::Char(c) => KeyCode::Char(c),
|
|
||||||
Self::Null => KeyCode::Null,
|
|
||||||
Self::Esc => KeyCode::Esc,
|
|
||||||
Self::CapsLock => KeyCode::CapsLock,
|
|
||||||
Self::ScrollLock => KeyCode::ScrollLock,
|
|
||||||
Self::NumLock => KeyCode::NumLock,
|
|
||||||
Self::PrintScreen => KeyCode::PrintScreen,
|
|
||||||
Self::Pause => KeyCode::Pause,
|
|
||||||
Self::Menu => KeyCode::Menu,
|
|
||||||
Self::KeypadBegin => KeyCode::KeypadBegin,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
use anyhow::Context;
|
|
||||||
|
|
||||||
use super::{Chars, Key, KeyValue};
|
|
||||||
|
|
||||||
use std::{fmt::Display, str::FromStr};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
|
|
||||||
pub struct Keys(pub(super) Vec<Key>);
|
|
||||||
|
|
||||||
impl Keys {
|
|
||||||
pub fn new(initial_value: Key) -> Self {
|
|
||||||
Keys(vec![initial_value])
|
|
||||||
}
|
|
||||||
pub fn join(&self, key: Key) -> Keys {
|
|
||||||
let mut output = self.0.clone();
|
|
||||||
output.push(key);
|
|
||||||
Keys(output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct KeysIter {
|
|
||||||
keys: Keys,
|
|
||||||
index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct KeysIterB<'a> {
|
|
||||||
keys: &'a Keys,
|
|
||||||
index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoIterator for Keys {
|
|
||||||
type Item = Key;
|
|
||||||
type IntoIter = KeysIter;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
KeysIter {
|
|
||||||
keys: self,
|
|
||||||
index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for KeysIter {
|
|
||||||
type Item = Key;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let output;
|
|
||||||
if self.keys.0.len() <= self.index {
|
|
||||||
output = None;
|
|
||||||
} else {
|
|
||||||
output = Some(self.keys.0[self.index]);
|
|
||||||
}
|
|
||||||
self.index += 1;
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a Keys {
|
|
||||||
type Item = &'a Key;
|
|
||||||
type IntoIter = KeysIterB<'a>;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
KeysIterB {
|
|
||||||
keys: self,
|
|
||||||
index: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for KeysIterB<'a> {
|
|
||||||
type Item = &'a Key;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let output;
|
|
||||||
output = self.keys.0.get(self.index);
|
|
||||||
self.index += 1;
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Keys {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(
|
|
||||||
&self
|
|
||||||
.into_iter()
|
|
||||||
.map(|key| key.to_string_repr())
|
|
||||||
.collect::<String>(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Keys {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let mut output: Vec<Key> = vec![];
|
|
||||||
let mut chars = Chars(s.chars().collect());
|
|
||||||
while let Some(char) = chars.pop() {
|
|
||||||
match char {
|
|
||||||
'<' => {
|
|
||||||
chars.prepend('<');
|
|
||||||
let key = Key::parse(&mut chars)
|
|
||||||
.with_context(|| format!("Failed to parse keymapping ('{}')", &chars))?;
|
|
||||||
output.push(key)
|
|
||||||
}
|
|
||||||
other_char => output.push(Key {
|
|
||||||
alt: false,
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
value: Some(KeyValue::Char(other_char)),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Keys(output))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,160 +0,0 @@
|
||||||
pub mod key;
|
|
||||||
pub mod keys;
|
|
||||||
pub mod chars;
|
|
||||||
pub mod key_value;
|
|
||||||
|
|
||||||
pub use key::*;
|
|
||||||
pub use keys::*;
|
|
||||||
pub(self) use chars::*;
|
|
||||||
pub use key_value::*;
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::app::keymappings::key::{Key, KeyValue};
|
|
||||||
use anyhow::Error;
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
|
|
||||||
use super::Keys;
|
|
||||||
|
|
||||||
// "<C-a>ba" => "Ctrl+a" && "b" && "a"
|
|
||||||
// "<S-a>" => "A" || "Shift+a"
|
|
||||||
// "A" => "A"
|
|
||||||
// "<M-a> " => "Alt+a" || "Super+a"
|
|
||||||
// "a<C-b><C-a>" => "a" && "Ctrl+b" && "Ctrl+a"
|
|
||||||
// "<CSM-b>" => "Ctrl+Shift+Alt+b"
|
|
||||||
// "a " => "a" && " "
|
|
||||||
// "å🙂" => "å" && "🙂"
|
|
||||||
// "<ESC>" => escape key
|
|
||||||
// "<BACKSPACE>" => backspace key (and so forth)
|
|
||||||
// "<DASH>" => "-"
|
|
||||||
// "<ANGULAR_BRACKET_OPEN>" || "<ABO>" => "<"
|
|
||||||
// "<ANGULAR_BRACKET_CLOSE>" || "<ABC>" => ">"
|
|
||||||
#[test]
|
|
||||||
fn test_simple() {
|
|
||||||
let keys: Keys = "<C-a>".parse().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
keys,
|
|
||||||
Keys(vec![Key {
|
|
||||||
alt: false,
|
|
||||||
ctrl: true,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
value: Some(KeyValue::Char('a'))
|
|
||||||
}])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_string_repr() {
|
|
||||||
let key = Key {
|
|
||||||
alt: true,
|
|
||||||
ctrl: false,
|
|
||||||
meta: true,
|
|
||||||
shift: true,
|
|
||||||
value: Some(KeyValue::Up),
|
|
||||||
};
|
|
||||||
assert_eq!("<AMS-<UP>>".to_owned(), key.to_string_repr());
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn test_string_repr_special() {
|
|
||||||
let key = Key {
|
|
||||||
alt: true,
|
|
||||||
ctrl: false,
|
|
||||||
meta: true,
|
|
||||||
shift: true,
|
|
||||||
value: Some(KeyValue::Char('<')),
|
|
||||||
};
|
|
||||||
assert_eq!("<AMS-<ANGULAR_BRACKET_OPEN>>".to_owned(), key.to_string_repr());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_extra_special_keys() {
|
|
||||||
// The <C--> part works! Although we should probably not encourage it
|
|
||||||
let keys: Keys = "<C--><C-<DASH>><A-<ABO>><ABC>".parse().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
keys,
|
|
||||||
Keys(vec![
|
|
||||||
Key {
|
|
||||||
alt: false,
|
|
||||||
ctrl: true,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
value: Some(KeyValue::Char('-'))
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
alt: false,
|
|
||||||
ctrl: true,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
value: Some(KeyValue::Char('-'))
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
alt: true,
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
value: Some(KeyValue::Char('<'))
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
alt: false,
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
value: Some(KeyValue::Char('>'))
|
|
||||||
},
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_false_pattern() {
|
|
||||||
let keys: Result<Keys, Error> = "<c->".parse();
|
|
||||||
assert!(keys.is_err())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_complex() {
|
|
||||||
let keys: Keys = "<C-a><ACMS-<ESC>>a 🙂".parse().unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
keys,
|
|
||||||
Keys(vec![
|
|
||||||
Key {
|
|
||||||
alt: false,
|
|
||||||
ctrl: true,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
value: Some(KeyValue::Char('a'))
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
alt: true,
|
|
||||||
ctrl: true,
|
|
||||||
meta: true,
|
|
||||||
shift: true,
|
|
||||||
value: Some(KeyValue::Esc)
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
alt: false,
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
value: Some(KeyValue::Char('a'))
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
alt: false,
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
value: Some(KeyValue::Char(' '))
|
|
||||||
},
|
|
||||||
Key {
|
|
||||||
alt: false,
|
|
||||||
ctrl: false,
|
|
||||||
meta: false,
|
|
||||||
shift: false,
|
|
||||||
value: Some(KeyValue::Char('🙂'))
|
|
||||||
},
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod key;
|
|
||||||
pub mod trie;
|
|
|
@ -1,237 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use anyhow::bail;
|
|
||||||
use cli_log::info;
|
|
||||||
|
|
||||||
use super::key::{Key, Keys};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub struct Node<V: std::fmt::Display> {
|
|
||||||
children: HashMap<Key, Box<Node<V>>>,
|
|
||||||
value: Option<V>,
|
|
||||||
is_terminal: bool,
|
|
||||||
is_child: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: std::fmt::Display> Node<V> {
|
|
||||||
pub fn new() -> Node<V> {
|
|
||||||
Node {
|
|
||||||
children: HashMap::new(),
|
|
||||||
is_terminal: true,
|
|
||||||
is_child: false,
|
|
||||||
value: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn new_child() -> Node<V> {
|
|
||||||
Node {
|
|
||||||
children: HashMap::new(),
|
|
||||||
is_terminal: true,
|
|
||||||
is_child: true,
|
|
||||||
value: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_terminal(&self) -> bool {
|
|
||||||
self.is_terminal
|
|
||||||
}
|
|
||||||
pub fn is_child(&self) -> bool {
|
|
||||||
self.is_child
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a reference to a child value of this node by key, will return None if the key does not exist.
|
|
||||||
/// If the key does exists, but does not have any values associated with it, it will return an
|
|
||||||
/// empty vector
|
|
||||||
/// The boolean denotes if the returned node is a true end or just a waypoint. It should be
|
|
||||||
/// called, when the bool is true
|
|
||||||
pub fn get(&self, keys: &Keys) -> Option<(Vec<&Box<Node<V>>>, bool)> {
|
|
||||||
let mut current_node = self;
|
|
||||||
let mut old_node = None;
|
|
||||||
for char in keys {
|
|
||||||
// r
|
|
||||||
// |
|
|
||||||
// a
|
|
||||||
// / \
|
|
||||||
// b a
|
|
||||||
// |
|
|
||||||
// c
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// r->a->a: Some([a]) -> a.is_child() && a.is_terminal() ? call a : *key input pending*
|
|
||||||
// r->a->b: Some([b]) -> b.is_child() && b.is_terminal() ? *call b* : key input pending
|
|
||||||
// r->a->c: None -> continue
|
|
||||||
// r->a->a->c: Some([c]) -> c.is_child() && c.is_terminal() ? *call c* : key input pending
|
|
||||||
//
|
|
||||||
// r->a: Some([a, b]) -> key input pending
|
|
||||||
if let Some(node) = current_node.children.get(&char) {
|
|
||||||
old_node = Some((current_node, char));
|
|
||||||
current_node = node;
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if current_node.is_child() && current_node.is_terminal() {
|
|
||||||
let (on, char) = old_node.expect("At this point, this should be Some");
|
|
||||||
info!("Returning calling node for char: '{}'", char);
|
|
||||||
Some((
|
|
||||||
vec![on
|
|
||||||
.children
|
|
||||||
.get(&char)
|
|
||||||
.expect("This should be some, as this was checked above")],
|
|
||||||
true,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Some((current_node.children.values().collect(), false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert a key value pair into the trie. The key is supplied as a string to facilitate the
|
|
||||||
/// creation of nested nodes.
|
|
||||||
pub fn insert(&mut self, keys: &Keys, value: V) -> anyhow::Result<()> {
|
|
||||||
let mut current_node = self;
|
|
||||||
for char in keys {
|
|
||||||
let child_node = current_node
|
|
||||||
.children
|
|
||||||
.entry(char.to_owned())
|
|
||||||
.or_insert(Box::new(Node::new_child()));
|
|
||||||
if current_node.value.is_some() {
|
|
||||||
bail!(
|
|
||||||
"The key ('{}') contains nodes, which already have a value set!",
|
|
||||||
keys
|
|
||||||
);
|
|
||||||
}
|
|
||||||
current_node.is_terminal = false;
|
|
||||||
current_node = child_node
|
|
||||||
}
|
|
||||||
if current_node.value.is_some() {
|
|
||||||
bail!(
|
|
||||||
"The key ('{}') is already set! The value is: '{}'",
|
|
||||||
keys,
|
|
||||||
current_node.value.as_ref().expect("This should be set")
|
|
||||||
)
|
|
||||||
} else if current_node.children.len() > 0 {
|
|
||||||
bail!(
|
|
||||||
"The node accessed by this key ('{}') has children! You can't set a value for it",
|
|
||||||
keys
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
current_node.value = Some(value);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the values from this node. If the node does not have a value associated with it,
|
|
||||||
/// return None
|
|
||||||
pub fn value(&self) -> Option<&V> {
|
|
||||||
self.value.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collect all values from this nodes children. Can be called recursively as it should be
|
|
||||||
/// tail-recursive.
|
|
||||||
pub fn collect_values_all(&self) -> Vec<&V> {
|
|
||||||
if self.is_terminal && self.is_child {
|
|
||||||
vec![self.value.as_ref().expect("We checked above")]
|
|
||||||
} else {
|
|
||||||
let out: Vec<_> = self
|
|
||||||
.children
|
|
||||||
.values()
|
|
||||||
.map(|node| node.collect_values_all())
|
|
||||||
.flatten()
|
|
||||||
.collect();
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use crate::app::keymappings::key::{Key, Keys};
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
|
|
||||||
use super::Node;
|
|
||||||
|
|
||||||
fn i(str: &str) -> Keys {
|
|
||||||
str.parse().unwrap()
|
|
||||||
}
|
|
||||||
fn k(str: &str) -> Key {
|
|
||||||
str.parse::<Key>().unwrap()
|
|
||||||
}
|
|
||||||
fn collect<V: std::fmt::Display>(nodes: Option<(Vec<&Box<Node<V>>>, bool)>) -> Vec<&V> {
|
|
||||||
let (nodes, _should_call) = nodes.unwrap();
|
|
||||||
nodes
|
|
||||||
.iter()
|
|
||||||
.map(|node| node.value())
|
|
||||||
.filter_map(|x| x)
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_empty_values() {
|
|
||||||
let trie: Node<bool> = Node::new();
|
|
||||||
assert_eq!(trie.collect_values_all(), Vec::<&bool>::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_insert() {
|
|
||||||
let mut trie: Node<bool> = Node::new();
|
|
||||||
|
|
||||||
trie.insert(&i("abc"), true).unwrap();
|
|
||||||
trie.insert(&i("abd"), true).unwrap();
|
|
||||||
trie.insert(&i("aca"), false).unwrap();
|
|
||||||
|
|
||||||
let output: Vec<&bool> = vec![&true, &true];
|
|
||||||
let get_output: Vec<_> = collect(trie.get(&i("ab")));
|
|
||||||
assert_eq!(get_output, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_duplicate_insert() {
|
|
||||||
let mut trie: Node<bool> = Node::new();
|
|
||||||
|
|
||||||
trie.insert(&i("abc"), true).unwrap();
|
|
||||||
trie.insert(&i("aca"), true).unwrap();
|
|
||||||
let output = trie.insert(&i("ab"), false);
|
|
||||||
|
|
||||||
dbg!(&trie);
|
|
||||||
assert!(output.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_multiple_get() {
|
|
||||||
// | | | | |
|
|
||||||
// a <- Input 1st a | q | i
|
|
||||||
// / \ | |
|
|
||||||
// b a <- Input 2nd a | |
|
|
||||||
// / \ | |
|
|
||||||
// d e
|
|
||||||
let mut trie: Node<bool> = Node::new();
|
|
||||||
let mut output: Node<bool> = Node::new();
|
|
||||||
|
|
||||||
trie.insert(&i("aa"), true).unwrap();
|
|
||||||
trie.insert(&i("abd"), true).unwrap();
|
|
||||||
trie.insert(&i("abe"), true).unwrap();
|
|
||||||
|
|
||||||
output.insert(&i("abd"), true).unwrap();
|
|
||||||
output.insert(&i("abe"), true).unwrap();
|
|
||||||
output.insert(&i("aa"), true).unwrap();
|
|
||||||
output.insert(&i("acd"), true).unwrap();
|
|
||||||
output.insert(&i("ace"), true).unwrap();
|
|
||||||
|
|
||||||
let a_children = &output.children.get(&k("a")).unwrap();
|
|
||||||
let output_nodes = vec![&a_children.children[&k("a")], &a_children.children[&k("b")]];
|
|
||||||
|
|
||||||
let (nodes, _should_call) = trie.get(&i("a")).unwrap();
|
|
||||||
assert_eq!(nodes, output_nodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_wrong_get() {
|
|
||||||
let mut trie: Node<bool> = Node::new();
|
|
||||||
|
|
||||||
trie.insert(&i("abc"), true).unwrap();
|
|
||||||
trie.insert(&i("abd"), true).unwrap();
|
|
||||||
trie.insert(&i("aca"), false).unwrap();
|
|
||||||
|
|
||||||
assert!(trie.get(&i("bb")).is_none());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +1,9 @@
|
||||||
pub mod command_interface;
|
pub mod command_interface;
|
||||||
pub mod keymappings;
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
use std::{
|
use std::path::{Path, PathBuf};
|
||||||
collections::HashMap,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{Context, Error, Result};
|
use anyhow::{Context, Error, Result};
|
||||||
use cli_log::{info, warn};
|
use cli_log::{info, warn};
|
||||||
|
@ -16,12 +12,7 @@ use matrix_sdk::Client;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use self::{
|
use self::{command_interface::lua_command_manager::LuaCommandManager, events::event_types};
|
||||||
command_interface::{
|
|
||||||
command_transfer_value::support_types::Function, lua_command_manager::LuaCommandManager,
|
|
||||||
},
|
|
||||||
events::event_types, keymappings::trie::Node,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
accounts::{Account, AccountsManager},
|
accounts::{Account, AccountsManager},
|
||||||
app::{
|
app::{
|
||||||
|
@ -31,8 +22,8 @@ use crate::{
|
||||||
ui::{central, setup},
|
ui::{central, setup},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct App<'runtime> {
|
pub struct App<'ui> {
|
||||||
ui: central::UI<'runtime>,
|
ui: central::UI<'ui>,
|
||||||
accounts_manager: AccountsManager,
|
accounts_manager: AccountsManager,
|
||||||
status: Status,
|
status: Status,
|
||||||
|
|
||||||
|
@ -45,8 +36,6 @@ pub struct App<'runtime> {
|
||||||
lua: LuaCommandManager,
|
lua: LuaCommandManager,
|
||||||
|
|
||||||
project_dirs: ProjectDirs,
|
project_dirs: ProjectDirs,
|
||||||
|
|
||||||
key_mappings: HashMap<State, Node<Function>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App<'_> {
|
impl App<'_> {
|
||||||
|
@ -77,7 +66,6 @@ impl App<'_> {
|
||||||
"Failed to allocate project direcectory paths, \
|
"Failed to allocate project direcectory paths, \
|
||||||
please ensure your $HOME is set correctly",
|
please ensure your $HOME is set correctly",
|
||||||
)?,
|
)?,
|
||||||
key_mappings: HashMap::new(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
use anyhow::{bail, Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use cli_log::warn;
|
use cli_log::warn;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
|
@ -12,36 +12,12 @@ use matrix_sdk::{
|
||||||
Client,
|
Client,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::keymappings::key::Keys;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Debug)]
|
|
||||||
pub enum State {
|
pub enum State {
|
||||||
Normal,
|
Normal,
|
||||||
Insert,
|
Insert,
|
||||||
Command,
|
Command,
|
||||||
/// Temporary workaround until command based login is working
|
/// Temporary workaround until command based login is working
|
||||||
Setup,
|
Setup,
|
||||||
/// Only used internally to signal, that we are waiting on further keyinputs, if multiple
|
|
||||||
/// keymappings have the same prefix
|
|
||||||
KeyInputPending {
|
|
||||||
old_state: Box<State>,
|
|
||||||
pending_keys: Keys,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
pub fn from_char(c: &char) -> Result<Self> {
|
|
||||||
Ok(match c {
|
|
||||||
'n' => State::Normal,
|
|
||||||
'i' => State::Insert,
|
|
||||||
'c' => State::Command,
|
|
||||||
's' => State::Setup,
|
|
||||||
_ => bail!(
|
|
||||||
"The letter '{}' is either not connected to a state or not yet implemented",
|
|
||||||
c
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
|
@ -84,10 +60,6 @@ impl fmt::Display for State {
|
||||||
Self::Insert => write!(f, "Insert"),
|
Self::Insert => write!(f, "Insert"),
|
||||||
Self::Command => write!(f, "Command"),
|
Self::Command => write!(f, "Command"),
|
||||||
Self::Setup => write!(f, "Setup (!! workaround !!)"),
|
Self::Setup => write!(f, "Setup (!! workaround !!)"),
|
||||||
Self::KeyInputPending {
|
|
||||||
old_state: _,
|
|
||||||
pending_keys: _,
|
|
||||||
} => write!(f, "Key Input Pending"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue