Merge: PR-11 (commands) -> master
This commit is contained in:
commit
dcf87f257d
|
@ -75,9 +75,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.71"
|
version = "1.0.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anymap2"
|
name = "anymap2"
|
||||||
|
@ -138,9 +138,9 @@ version = "0.3.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
|
checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -149,9 +149,9 @@ version = "0.1.71"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf"
|
checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -415,6 +415,15 @@ version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
|
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "convert_case"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
@ -547,8 +556,8 @@ checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fnv",
|
"fnv",
|
||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
@ -560,18 +569,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
|
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dashmap"
|
name = "dashmap"
|
||||||
version = "5.4.0"
|
version = "5.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
|
checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown 0.14.0",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot_core 0.9.8",
|
"parking_lot_core 0.9.8",
|
||||||
|
@ -602,8 +611,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4"
|
checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -649,9 +658,9 @@ version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -696,9 +705,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
|
@ -848,9 +857,9 @@ version = "0.3.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1213,9 +1222,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.8"
|
version = "1.0.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
|
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
|
@ -1291,9 +1300,10 @@ dependencies = [
|
||||||
name = "lua_macros"
|
name = "lua_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"convert_case",
|
||||||
"quote 1.0.29",
|
"proc-macro2 1.0.66",
|
||||||
"syn 2.0.25",
|
"quote 1.0.31",
|
||||||
|
"syn 2.0.26",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1523,6 +1533,23 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mlua"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07366ed2cd22a3b000aed076e2b68896fb46f06f1f5786c5962da73c0af01577"
|
||||||
|
dependencies = [
|
||||||
|
"bstr",
|
||||||
|
"cc",
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
"num-traits",
|
||||||
|
"once_cell",
|
||||||
|
"pkg-config",
|
||||||
|
"rustc-hash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "native-tls"
|
name = "native-tls"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
|
@ -1602,9 +1629,9 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1717,9 +1744,9 @@ version = "1.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
|
checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1788,9 +1815,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.64"
|
version = "1.0.66"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da"
|
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
@ -1822,8 +1849,8 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools",
|
"itertools",
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1838,11 +1865,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.29"
|
version = "1.0.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
|
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2063,9 +2090,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf"
|
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -2074,9 +2101,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.7.3"
|
version = "0.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846"
|
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
|
@ -2115,30 +2142,6 @@ dependencies = [
|
||||||
"winreg",
|
"winreg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rlua"
|
|
||||||
version = "0.19.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5d33e5ba15c3d43178f283ed5863d4531e292fc0e56fb773f3bea45f18e3a42a"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"bstr",
|
|
||||||
"libc",
|
|
||||||
"num-traits",
|
|
||||||
"rlua-lua54-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rlua-lua54-sys"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7aafabafe1895cb4a2be81a56d7ff3d46bf4b5d2f9cfdbea2ed404cdabe96474"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
"pkg-config",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma"
|
name = "ruma"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
|
@ -2230,8 +2233,8 @@ checksum = "0f82e91eb61cd86d9287303133ee55b54618eccb75a522cc22a42c15f5bda340"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"ruma-identifiers-validation",
|
"ruma-identifiers-validation",
|
||||||
"serde",
|
"serde",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
|
@ -2244,6 +2247,12 @@ version = "0.1.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.37.23"
|
version = "0.37.23"
|
||||||
|
@ -2260,9 +2269,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.14"
|
version = "1.0.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
|
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
|
@ -2304,38 +2313,38 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.169"
|
version = "1.0.171"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd51c3db8f9500d531e6c12dd0fd4ad13d133e9117f5aebac3cdbb8b6d9824b0"
|
checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_bytes"
|
name = "serde_bytes"
|
||||||
version = "0.11.11"
|
version = "0.11.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a16be4fe5320ade08736447e3198294a5ea9a6d44dde6f35f0a5e06859c427a"
|
checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.169"
|
version = "1.0.171"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "27738cfea0d944ab72c3ed01f3d5f23ec4322af8a1431e40ce630e4c01ea74fd"
|
checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.100"
|
version = "1.0.103"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c"
|
checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -2380,9 +2389,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.3.15"
|
version = "0.3.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
|
checksum = "b824b6e687aff278cdbf3b36f07aa52d4bd4099699324d5da86a2ebce3aa00b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
|
@ -2493,19 +2502,19 @@ version = "1.0.109"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.25"
|
version = "2.0.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2"
|
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2538,9 +2547,9 @@ version = "1.0.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
|
checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2593,9 +2602,9 @@ version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2639,9 +2648,9 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.19.12"
|
version = "0.19.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78"
|
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.0.0",
|
"indexmap 2.0.0",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
|
@ -2672,9 +2681,9 @@ version = "0.1.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2696,7 +2705,7 @@ dependencies = [
|
||||||
"indexmap 2.0.0",
|
"indexmap 2.0.0",
|
||||||
"lua_macros",
|
"lua_macros",
|
||||||
"matrix-sdk",
|
"matrix-sdk",
|
||||||
"rlua",
|
"mlua",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -2747,9 +2756,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.10"
|
version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73"
|
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
|
@ -2912,9 +2921,9 @@ dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2936,7 +2945,7 @@ version = "0.2.87"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
|
checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2946,9 +2955,9 @@ version = "0.2.87"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
@ -3089,9 +3098,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.4.9"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529"
|
checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -3132,7 +3141,7 @@ version = "1.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2 1.0.64",
|
"proc-macro2 1.0.66",
|
||||||
"quote 1.0.29",
|
"quote 1.0.31",
|
||||||
"syn 2.0.25",
|
"syn 2.0.26",
|
||||||
]
|
]
|
||||||
|
|
|
@ -18,4 +18,4 @@ tokio-util = "0.7"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
cli-log = "2.0"
|
cli-log = "2.0"
|
||||||
indexmap = "2.0.0"
|
indexmap = "2.0.0"
|
||||||
rlua = "0.19.7"
|
mlua = { version = "0.8.9", features = ["lua54", "async", "send"] }
|
||||||
|
|
18
flake.lock
18
flake.lock
|
@ -50,11 +50,11 @@
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1687709756,
|
"lastModified": 1689068808,
|
||||||
"narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=",
|
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7",
|
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -65,11 +65,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1688829822,
|
"lastModified": 1689449371,
|
||||||
"narHash": "sha256-hv56yK1fPHPt7SU2DboxBtdSbIuv9nym7Dss7Cn2jic=",
|
"narHash": "sha256-sK3Oi8uEFrFPL83wKPV6w0+96NrmwqIpw9YFffMifVg=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ed6afb10dfdfc97b6bcf0703f1bad8118e9e961b",
|
"rev": "29bcead8405cfe4c00085843eb372cc43837bb9d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -98,11 +98,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1688870171,
|
"lastModified": 1689561325,
|
||||||
"narHash": "sha256-8tD8fheWPa7TaJoxzcU3iHkCrQQpOpdMN+HYqgZ1N5A=",
|
"narHash": "sha256-+UABrHUXtWJSc9mM7oEKPIYQEhTzUVVNy2IPG9Lfrj0=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "5a932f10ac4bd59047d6e8b5780750ec76ea988a",
|
"rev": "d8a38aea13c67dc2ce10cff93eb274dcf455753f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
12
flake.nix
12
flake.nix
|
@ -45,16 +45,20 @@
|
||||||
overlays = [(import rust-overlay)];
|
overlays = [(import rust-overlay)];
|
||||||
};
|
};
|
||||||
|
|
||||||
#rust-nightly = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default);
|
nightly = true;
|
||||||
rust-stable = pkgs.rust-bin.stable.latest.default;
|
rust =
|
||||||
|
if nightly
|
||||||
|
then pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default)
|
||||||
|
else pkgs.rust-bin.stable.latest.default;
|
||||||
|
|
||||||
craneLib = (crane.mkLib pkgs).overrideToolchain rust-stable;
|
craneLib = (crane.mkLib pkgs).overrideToolchain rust;
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
pkg-config
|
pkg-config
|
||||||
];
|
];
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
openssl
|
openssl
|
||||||
|
lua54Packages.stdlib
|
||||||
];
|
];
|
||||||
|
|
||||||
craneBuild = craneLib.buildPackage {
|
craneBuild = craneLib.buildPackage {
|
||||||
|
@ -78,7 +82,7 @@
|
||||||
statix
|
statix
|
||||||
ltex-ls
|
ltex-ls
|
||||||
|
|
||||||
rust-stable
|
rust
|
||||||
rust-analyzer
|
rust-analyzer
|
||||||
cargo-edit
|
cargo-edit
|
||||||
cargo-expand
|
cargo-expand
|
||||||
|
|
|
@ -4,9 +4,10 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate_type = ["proc-macro"]
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
convert_case = "0.6.0"
|
||||||
proc-macro2 = "1.0.64"
|
proc-macro2 = "1.0.64"
|
||||||
quote = "1.0.29"
|
quote = "1.0.29"
|
||||||
syn = "2.0.25"
|
syn = { version = "2.0.25", features = ["extra-traits", "full", "parsing"] }
|
||||||
|
|
|
@ -1,59 +1,69 @@
|
||||||
|
mod mark_as_ci_command;
|
||||||
|
mod struct_to_ci_enum;
|
||||||
|
|
||||||
|
use mark_as_ci_command::generate_final_function;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn;
|
use struct_to_ci_enum::{generate_command_enum, generate_generate_ci_function, generate_help_function};
|
||||||
|
use syn::{self, parse_quote, parse_str, DeriveInput, FieldMutability, ItemFn, Token, Visibility};
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn generate_ci_functions(_: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn turn_struct_to_ci_command_enum(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
// Construct a representation of Rust code as a syntax tree
|
// Construct a representation of Rust code as a syntax tree
|
||||||
// that we can manipulate
|
// that we can manipulate
|
||||||
let input = syn::parse(input)
|
let mut input: DeriveInput = syn::parse(input).expect("This should always be valid rust code, as it's extracted from direct code");
|
||||||
.expect("This should always be valid rust code, as it's extracted from direct code");
|
|
||||||
|
let mut named_fields = match &input.data {
|
||||||
|
syn::Data::Struct(input) => match &input.fields {
|
||||||
|
syn::Fields::Named(named_fields) => named_fields,
|
||||||
|
_ => unimplemented!("The macro only works for named fields (e.g.: `Name: Type`)"),
|
||||||
|
},
|
||||||
|
_ => unimplemented!("The macro only works for structs"),
|
||||||
|
}
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
let attr_parsed = parse_quote! {
|
||||||
|
/// This is a help function
|
||||||
|
};
|
||||||
|
|
||||||
|
named_fields.named.push(syn::Field {
|
||||||
|
attrs: vec![attr_parsed],
|
||||||
|
// attrs: attr_parser
|
||||||
|
// .parse("#[doc = r\"This is a help function\"]".to_token_stream().into())
|
||||||
|
// .expect("See reason for other one"),
|
||||||
|
vis: Visibility::Inherited,
|
||||||
|
mutability: FieldMutability::None,
|
||||||
|
ident: Some(format_ident!("help")),
|
||||||
|
colon_token: Some(Token![:](Span::call_site())),
|
||||||
|
ty: parse_str("fn(Option<String>) -> String").expect("This is static and valid rust code"),
|
||||||
|
});
|
||||||
|
|
||||||
|
match &mut input.data {
|
||||||
|
syn::Data::Struct(input) => input.fields = syn::Fields::Named(named_fields.clone()),
|
||||||
|
_ => unreachable!("This was a DataStruct before"),
|
||||||
|
};
|
||||||
|
|
||||||
// Build the trait implementation
|
// Build the trait implementation
|
||||||
generate_generate_ci_functions(&input)
|
let generate_ci_function: TokenStream2 = generate_generate_ci_function(&input);
|
||||||
}
|
|
||||||
|
let command_enum = generate_command_enum(&named_fields);
|
||||||
|
|
||||||
|
let help_function = generate_help_function(&named_fields);
|
||||||
|
|
||||||
fn generate_generate_ci_functions(input: &syn::DeriveInput) -> TokenStream {
|
|
||||||
let input_tokens: TokenStream2 = match &input.data {
|
|
||||||
syn::Data::Struct(input) => match &input.fields {
|
|
||||||
syn::Fields::Named(named_fields) => named_fields
|
|
||||||
.named
|
|
||||||
.iter()
|
|
||||||
.map(|field| -> TokenStream2 {
|
|
||||||
let field_ident = field.ident.as_ref().expect(
|
|
||||||
"These are only the named field, thus they all should have a name.",
|
|
||||||
);
|
|
||||||
let function_name_ident = format_ident!("fun_{}", field_ident);
|
|
||||||
let function_name = format!("{}", field_ident);
|
|
||||||
quote! {
|
quote! {
|
||||||
let #function_name_ident = context.create_function(#field_ident).expect(
|
#command_enum
|
||||||
&format!(
|
|
||||||
"The function: `{}` should be defined",
|
#generate_ci_function
|
||||||
#function_name
|
|
||||||
)
|
//#help_function
|
||||||
);
|
|
||||||
globals.set(#function_name, #function_name_ident).expect(
|
|
||||||
&format!(
|
|
||||||
"Setting a static global value ({}, fun_{}) should work",
|
|
||||||
#function_name,
|
|
||||||
#function_name
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
})
|
}
|
||||||
.collect(),
|
|
||||||
_ => unimplemented!("Only implemented for named fileds"),
|
|
||||||
},
|
|
||||||
_ => unimplemented!("Only for implemented for structs"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let gen = quote! {
|
#[proc_macro_attribute]
|
||||||
pub fn generate_ci_functions(context: &mut Context) {
|
pub fn ci_command(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
let globals = context.globals();
|
let mut input: ItemFn = syn::parse(input).expect("This should always be valid rust code, as it's extracted from direct code");
|
||||||
#input_tokens
|
let output_function = generate_final_function(&mut input);
|
||||||
}
|
output_function.into()
|
||||||
};
|
|
||||||
gen.into()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{format_ident, quote, ToTokens};
|
||||||
|
use syn::{Block, Expr, ExprBlock, GenericArgument, ReturnType, Stmt, Type};
|
||||||
|
|
||||||
|
pub fn generate_final_function(input: &mut syn::ItemFn) -> TokenStream2 {
|
||||||
|
append_tx_send_code(input);
|
||||||
|
|
||||||
|
let output: TokenStream2 = syn::parse(input.into_token_stream().into())
|
||||||
|
.expect("This is generated from valid rust code, it should stay that way.");
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn {
|
||||||
|
let function_name_pascal = format_ident!(
|
||||||
|
"{}",
|
||||||
|
input
|
||||||
|
.sig
|
||||||
|
.ident
|
||||||
|
.clone()
|
||||||
|
.to_string()
|
||||||
|
.from_case(Case::Snake)
|
||||||
|
.to_case(Case::Pascal)
|
||||||
|
);
|
||||||
|
|
||||||
|
let tx_send = match &input.sig.output {
|
||||||
|
syn::ReturnType::Default => {
|
||||||
|
unreachable!("All functions should have a output of (Result<$type, rlua::Error>)");
|
||||||
|
}
|
||||||
|
syn::ReturnType::Type(_, ret_type) => {
|
||||||
|
let return_type = match *(ret_type.clone()) {
|
||||||
|
syn::Type::Path(path) => {
|
||||||
|
match path
|
||||||
|
.path
|
||||||
|
.segments
|
||||||
|
.first()
|
||||||
|
.expect("This is expected to be only one path segment")
|
||||||
|
.arguments
|
||||||
|
.to_owned()
|
||||||
|
{
|
||||||
|
syn::PathArguments::AngleBracketed(angled_path) => {
|
||||||
|
let angled_path = angled_path.args.to_owned();
|
||||||
|
let filtered_paths: Vec<_> = angled_path
|
||||||
|
.into_iter()
|
||||||
|
.filter(|generic_arg| {
|
||||||
|
if let GenericArgument::Type(generic_type) = generic_arg {
|
||||||
|
if let Type::Path(_) = generic_type {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// There should only be two segments (the type is <String, rlua::Error>)
|
||||||
|
if filtered_paths.len() > 2 {
|
||||||
|
unreachable!("There should be no more than two filtered_output, but got: {:#?}", filtered_paths)
|
||||||
|
} else if filtered_paths.len() <= 0 {
|
||||||
|
unreachable!("There should be more than zero filtered_output, but got: {:#?}", filtered_paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filtered_paths.len() == 2 {
|
||||||
|
// There is something else than mlua::Error
|
||||||
|
let gen_type = if let GenericArgument::Type(ret_type) =
|
||||||
|
filtered_paths
|
||||||
|
.first()
|
||||||
|
.expect("One path segment should exists")
|
||||||
|
.to_owned()
|
||||||
|
{
|
||||||
|
ret_type
|
||||||
|
} else {
|
||||||
|
unreachable!("These were filtered above.");
|
||||||
|
};
|
||||||
|
let return_type_as_type_prepared = quote! {-> #gen_type};
|
||||||
|
|
||||||
|
let return_type_as_return_type: ReturnType = syn::parse(
|
||||||
|
return_type_as_type_prepared.to_token_stream().into(),
|
||||||
|
)
|
||||||
|
.expect("This is valid.");
|
||||||
|
return_type_as_return_type
|
||||||
|
} else {
|
||||||
|
// There is only mlua::Error left
|
||||||
|
ReturnType::Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!("Only for angled paths"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!("Only for path types"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let send_data = match return_type {
|
||||||
|
ReturnType::Default => {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
Event::CommandEvent(Command::#function_name_pascal, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReturnType::Type(_, _) => {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
Event::CommandEvent(Command::#function_name_pascal(input.clone()), Some(callback_tx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let output_return = match return_type {
|
||||||
|
ReturnType::Default => {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReturnType::Type(_, _) => {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
if let Some(output) = callback_rx.recv().await {
|
||||||
|
callback_rx.close();
|
||||||
|
return Ok(output);
|
||||||
|
} else {
|
||||||
|
return Err(mlua::Error::ExternalError(Arc::new(Error::new(
|
||||||
|
ErrorKind::Other,
|
||||||
|
"Callback reciever dropped",
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
let (callback_tx, mut callback_rx) = tokio::sync::mpsc::channel::<String>(256);
|
||||||
|
|
||||||
|
let tx:
|
||||||
|
core::cell::Ref<tokio::sync::mpsc::Sender<crate::app::events::event_types::Event>> =
|
||||||
|
lua
|
||||||
|
.app_data_ref()
|
||||||
|
.expect("This exists, it was set before");
|
||||||
|
|
||||||
|
(*tx)
|
||||||
|
.try_send(#send_data)
|
||||||
|
.expect("This should work, as the reciever is not dropped");
|
||||||
|
|
||||||
|
#output_return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tx_send_block: Block =
|
||||||
|
syn::parse(tx_send.into()).expect("This is a static string, it will always parse");
|
||||||
|
|
||||||
|
let tx_send_expr_block = ExprBlock {
|
||||||
|
attrs: vec![],
|
||||||
|
label: None,
|
||||||
|
block: tx_send_block,
|
||||||
|
};
|
||||||
|
let mut tx_send_stmt = vec![Stmt::Expr(Expr::Block(tx_send_expr_block), None)];
|
||||||
|
|
||||||
|
let mut new_stmts: Vec<Stmt> = Vec::with_capacity(input.block.stmts.len() + 1);
|
||||||
|
new_stmts.append(&mut tx_send_stmt);
|
||||||
|
new_stmts.append(&mut input.block.stmts);
|
||||||
|
input.block.stmts = new_stmts;
|
||||||
|
input
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use syn::Type;
|
||||||
|
|
||||||
|
pub fn generate_command_enum(input: &syn::FieldsNamed) -> TokenStream2 {
|
||||||
|
let input_tokens: TokenStream2 = input
|
||||||
|
.named
|
||||||
|
.iter()
|
||||||
|
.map(|field| -> TokenStream2 {
|
||||||
|
let field_ident = field
|
||||||
|
.ident
|
||||||
|
.as_ref()
|
||||||
|
.expect("These are only the named fields, thus they should all have a ident.");
|
||||||
|
|
||||||
|
let enum_variant_type = match &field.ty {
|
||||||
|
syn::Type::BareFn(function) => {
|
||||||
|
let return_path = &function.inputs;
|
||||||
|
|
||||||
|
let input_type: Option<Type> = if return_path.len() == 1 {
|
||||||
|
Some(
|
||||||
|
return_path
|
||||||
|
.last()
|
||||||
|
.expect("The last element exists")
|
||||||
|
.ty
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
} else if return_path.len() == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
panic!("The Function can only take on argument, or none");
|
||||||
|
};
|
||||||
|
input_type
|
||||||
|
}
|
||||||
|
_ => unimplemented!("This is only implemented for bare function types"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let enum_variant_name = format_ident!(
|
||||||
|
"{}",
|
||||||
|
field_ident
|
||||||
|
.to_string()
|
||||||
|
.from_case(Case::Snake)
|
||||||
|
.to_case(Case::Pascal)
|
||||||
|
);
|
||||||
|
if enum_variant_type.is_some() {
|
||||||
|
quote! {
|
||||||
|
#enum_variant_name (#enum_variant_type),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#enum_variant_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let gen = quote! {
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Command {
|
||||||
|
#input_tokens
|
||||||
|
}
|
||||||
|
};
|
||||||
|
gen.into()
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use syn::{parse_quote, ReturnType, Type};
|
||||||
|
|
||||||
|
fn generate_ci_function_exposure(field: &syn::Field) -> TokenStream2 {
|
||||||
|
let field_ident = field
|
||||||
|
.ident
|
||||||
|
.as_ref()
|
||||||
|
.expect("These are only the named field, thus they all should have a name.");
|
||||||
|
|
||||||
|
let function_name_ident = format_ident!("fun_{}", field_ident);
|
||||||
|
let function_name = format!("{}", field_ident);
|
||||||
|
quote! {
|
||||||
|
let #function_name_ident = lua.create_async_function(#field_ident).expect(
|
||||||
|
&format!(
|
||||||
|
"The function: `{}` should be defined",
|
||||||
|
#function_name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
globals.set(#function_name, #function_name_ident).expect(
|
||||||
|
&format!(
|
||||||
|
"Setting a static global value ({}, fun_{}) should work",
|
||||||
|
#function_name,
|
||||||
|
#function_name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 {
|
||||||
|
let mut functions_to_generate: Vec<TokenStream2> = vec![];
|
||||||
|
|
||||||
|
let functions_to_export_in_lua: TokenStream2 = match &input.data {
|
||||||
|
syn::Data::Struct(input) => match &input.fields {
|
||||||
|
syn::Fields::Named(named_fields) => named_fields
|
||||||
|
.named
|
||||||
|
.iter()
|
||||||
|
.map(|field| -> TokenStream2 {
|
||||||
|
let input_type = match &field.ty {
|
||||||
|
syn::Type::BareFn(bare_fn) => {
|
||||||
|
if bare_fn.inputs.len() == 1 {
|
||||||
|
bare_fn.inputs.last().expect("The last element exists").ty.clone()
|
||||||
|
} else if bare_fn.inputs.len() == 0 {
|
||||||
|
let input_type: Type = parse_quote! {()};
|
||||||
|
input_type
|
||||||
|
} else {
|
||||||
|
panic!("The Function can only take on argument, or none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!("This is only implemented for bare function types"),
|
||||||
|
};
|
||||||
|
let return_type = match &field.ty {
|
||||||
|
syn::Type::BareFn(function) => {
|
||||||
|
let return_path: &ReturnType = &function.output;
|
||||||
|
match return_path {
|
||||||
|
ReturnType::Default => None,
|
||||||
|
ReturnType::Type(_, return_type) => Some(return_type.to_owned()) }
|
||||||
|
}
|
||||||
|
_ => unimplemented!("This is only implemented for bare function types"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let function_name = field
|
||||||
|
.ident
|
||||||
|
.as_ref()
|
||||||
|
.expect("These are only the named field, thus they all should have a name.");
|
||||||
|
|
||||||
|
if let Some(ret_type) = return_type {
|
||||||
|
functions_to_generate.push(quote! {
|
||||||
|
#[ci_command]
|
||||||
|
async fn #function_name(lua: &mlua::Lua, input: #input_type) -> Result<#ret_type, mlua::Error> {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
functions_to_generate.push(quote! {
|
||||||
|
#[ci_command]
|
||||||
|
async fn #function_name(lua: &mlua::Lua, input: #input_type) -> Result<(), mlua::Error> {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
generate_ci_function_exposure(field)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
|
||||||
|
_ => unimplemented!("Only implemented for named fileds"),
|
||||||
|
},
|
||||||
|
_ => unimplemented!("Only implemented for structs"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let functions_to_generate: TokenStream2 = functions_to_generate.into_iter().collect();
|
||||||
|
let gen = quote! {
|
||||||
|
pub fn generate_ci_functions(
|
||||||
|
lua: &mut mlua::Lua,
|
||||||
|
tx: tokio::sync::mpsc::Sender<crate::app::events::event_types::Event>
|
||||||
|
)
|
||||||
|
{
|
||||||
|
lua.set_app_data(tx);
|
||||||
|
let globals = lua.globals();
|
||||||
|
#functions_to_export_in_lua
|
||||||
|
}
|
||||||
|
#functions_to_generate
|
||||||
|
};
|
||||||
|
gen.into()
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
|
||||||
|
pub fn generate_help_function(input: &syn::FieldsNamed) -> TokenStream2 {
|
||||||
|
let input: Vec<_> = input.named.iter().collect();
|
||||||
|
|
||||||
|
let combined_help_text: TokenStream2 = input
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
let attrs_with_doc: Vec<TokenStream2> = field
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|attr| {
|
||||||
|
if attr.path().is_ident("doc") {
|
||||||
|
let help_text = attr
|
||||||
|
.meta
|
||||||
|
.require_name_value()
|
||||||
|
.expect("This is a named value type, because all doc comments work this way")
|
||||||
|
.value
|
||||||
|
.clone();
|
||||||
|
Some(help_text.into_token_stream().into())
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if attrs_with_doc.len() == 0 {
|
||||||
|
// TODO there should be a better panic function, than the generic one
|
||||||
|
panic!(
|
||||||
|
"The command named: `{}`, does not provide a help message",
|
||||||
|
field.ident.as_ref().expect("These are all named")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let help_text_for_one_command_combined: TokenStream2 = attrs_with_doc.into_iter().collect();
|
||||||
|
return help_text_for_one_command_combined;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[ci_command]
|
||||||
|
async fn help(
|
||||||
|
lua: &mlua::Lua,
|
||||||
|
input_str: Option<String>
|
||||||
|
) -> Result<String, mlua::Error> {
|
||||||
|
// TODO add a way to filter the help based on the input
|
||||||
|
|
||||||
|
let output = "These functions exist:\n";
|
||||||
|
output.push_str(#combined_help_text);
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
pub mod generate_command_enum;
|
||||||
|
pub mod generate_generate_ci_function;
|
||||||
|
pub mod generate_help_function;
|
||||||
|
|
||||||
|
pub use generate_command_enum::*;
|
||||||
|
pub use generate_generate_ci_function::*;
|
||||||
|
pub use generate_help_function::*;
|
|
@ -1,25 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use tokio::sync::mpsc;
|
|
||||||
|
|
||||||
use super::events::event_types::Event;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Command {
|
|
||||||
// Closes the application
|
|
||||||
Exit,
|
|
||||||
|
|
||||||
CommandLineShow,
|
|
||||||
CommandLineHide,
|
|
||||||
|
|
||||||
CyclePlanes,
|
|
||||||
CyclePlanesRev,
|
|
||||||
|
|
||||||
// sends a message to the current room
|
|
||||||
RoomMessageSend(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn execute(channel: &mpsc::Sender<Event>, command: Command) -> Result<()> {
|
|
||||||
let event = Event::CommandEvent(command);
|
|
||||||
channel.send(event).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,13 +1,49 @@
|
||||||
use lua_macros::generate_ci_functions;
|
// FIXME: This file needs documentation with examples of how the proc macros work.
|
||||||
use rlua::Context;
|
// for now use `cargo expand app::command_interface` for an overview
|
||||||
|
|
||||||
// This struct is here to gurantee, that all functions actually end up in the lua context.
|
use std::{io::{Error, ErrorKind}, sync::Arc};
|
||||||
// I. e. rust should throw a compile error, when one field is added, but not a matching function.
|
|
||||||
#[generate_ci_functions()]
|
|
||||||
struct Commands<'lua> {
|
|
||||||
greet: Function<'lua>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn greet(context: Context, name: String) -> Result<String, rlua::Error> {
|
use lua_macros::{ci_command, turn_struct_to_ci_command_enum};
|
||||||
Ok(format!("Name is {}", name))
|
|
||||||
|
use crate::app::event_types::Event;
|
||||||
|
/// This struct is here to guarantee, that all functions actually end up in the lua context.
|
||||||
|
/// I.e. Rust should throw a compile error, when one field is added, but not a matching function.
|
||||||
|
///
|
||||||
|
/// What it does:
|
||||||
|
/// - Generates a `generate_ci_functions` function, which wraps the specified rust in functions
|
||||||
|
/// in lua and exports them to the globals in the context provided as argument.
|
||||||
|
/// - Generates a Commands enum, which contains every Camel cased version of the fields.
|
||||||
|
///
|
||||||
|
/// Every command specified here should have a function named $command_name, where $command_name is the snake cased name of the field.
|
||||||
|
///
|
||||||
|
/// This function is exported to the lua context, thus it's signature must be:
|
||||||
|
/// ```rust
|
||||||
|
/// fn $command_name(context: Context, input_string: String) -> Result<$return_type, rlua::Error> {}
|
||||||
|
/// ```
|
||||||
|
/// where $return_type is the type returned by the function (the only supported ones are right now
|
||||||
|
/// `String` and `()`).
|
||||||
|
|
||||||
|
#[turn_struct_to_ci_command_enum]
|
||||||
|
struct Commands {
|
||||||
|
/// Greets the user
|
||||||
|
greet: fn(String) -> String,
|
||||||
|
|
||||||
|
/// Closes the application
|
||||||
|
//#[expose(lua)]
|
||||||
|
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(),
|
||||||
|
|
||||||
|
/// Send a message to the current room
|
||||||
|
/// The send message is interpreted literally.
|
||||||
|
room_message_send: fn(String) -> String,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,66 @@
|
||||||
use crate::app::{command::Command, events::event_types::EventStatus, App};
|
use crate::app::{command_interface::Command, events::event_types::EventStatus, App};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cli_log::info;
|
use cli_log::info;
|
||||||
|
|
||||||
pub async fn handle(app: &mut App<'_>, command: &Command) -> Result<EventStatus> {
|
pub async fn handle(
|
||||||
|
app: &mut App<'_>,
|
||||||
|
command: &Command,
|
||||||
|
send_output: bool,
|
||||||
|
) -> Result<(EventStatus, String)> {
|
||||||
|
macro_rules! set_status_output {
|
||||||
|
($str:expr) => {
|
||||||
|
if send_output {
|
||||||
|
app.ui.set_command_output($str);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($str:expr, $($args:ident),+) => {
|
||||||
|
if send_output {
|
||||||
|
app.ui.set_command_output(&format!($str, $($args),+));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
info!("Handling command: {:#?}", command);
|
info!("Handling command: {:#?}", command);
|
||||||
|
|
||||||
Ok(match command {
|
Ok(match command {
|
||||||
Command::Exit => EventStatus::Terminate,
|
Command::Exit => (
|
||||||
|
EventStatus::Terminate,
|
||||||
|
"Terminated the application".to_owned(),
|
||||||
|
),
|
||||||
|
|
||||||
Command::CommandLineShow => {
|
Command::CommandLineShow => {
|
||||||
app.ui.cli_enable();
|
app.ui.cli_enable();
|
||||||
EventStatus::Ok
|
set_status_output!("CLI online");
|
||||||
|
(EventStatus::Ok, "".to_owned())
|
||||||
}
|
}
|
||||||
Command::CommandLineHide => {
|
Command::CommandLineHide => {
|
||||||
app.ui.cli_disable();
|
app.ui.cli_disable();
|
||||||
EventStatus::Ok
|
set_status_output!("CLI offline");
|
||||||
|
(EventStatus::Ok, "".to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::CyclePlanes => {
|
Command::CyclePlanes => {
|
||||||
app.ui.cycle_main_input_position();
|
app.ui.cycle_main_input_position();
|
||||||
EventStatus::Ok
|
set_status_output!("Switched main input position");
|
||||||
|
(EventStatus::Ok, "".to_owned())
|
||||||
}
|
}
|
||||||
Command::CyclePlanesRev => {
|
Command::CyclePlanesRev => {
|
||||||
app.ui.cycle_main_input_position_rev();
|
app.ui.cycle_main_input_position_rev();
|
||||||
EventStatus::Ok
|
set_status_output!("Switched main input position; reversed");
|
||||||
|
(EventStatus::Ok, "".to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::RoomMessageSend(msg) => {
|
Command::RoomMessageSend(msg) => {
|
||||||
if let Some(room) = app.status.room_mut() {
|
if let Some(room) = app.status.room_mut() {
|
||||||
room.send(msg.clone()).await?;
|
room.send(msg.clone()).await?;
|
||||||
}
|
}
|
||||||
EventStatus::Ok
|
set_status_output!("Send message: `{}`", msg);
|
||||||
|
(EventStatus::Ok, "".to_owned())
|
||||||
}
|
}
|
||||||
|
Command::Greet(name) => {
|
||||||
|
info!("Greated {}", name);
|
||||||
|
set_status_output!("Hi, {}!", name);
|
||||||
|
(EventStatus::Ok, "".to_owned())
|
||||||
|
}
|
||||||
|
Command::Help(_) => todo!(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use cli_log::{debug, info};
|
||||||
|
use tokio::{task, time::timeout};
|
||||||
|
|
||||||
|
use crate::app::{events::event_types::EventStatus, App};
|
||||||
|
|
||||||
|
pub async fn handle(app: &mut App<'_>, command: String) -> Result<EventStatus> {
|
||||||
|
info!("Recieved ci command: `{command}`; executing..");
|
||||||
|
|
||||||
|
let local = task::LocalSet::new();
|
||||||
|
|
||||||
|
// Run the local task set.
|
||||||
|
let output = local
|
||||||
|
.run_until(async move {
|
||||||
|
let lua = Arc::clone(&app.lua);
|
||||||
|
debug!("before_handle");
|
||||||
|
let c_handle = task::spawn_local(async move {
|
||||||
|
lua.load(&command)
|
||||||
|
// FIXME this assumes string output only
|
||||||
|
.eval_async::<String>()
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to execute: `{command}`"))
|
||||||
|
});
|
||||||
|
debug!("after_handle");
|
||||||
|
c_handle
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
debug!("after_thread");
|
||||||
|
|
||||||
|
let output = timeout(Duration::from_secs(10), output)
|
||||||
|
.await
|
||||||
|
.context("Failed to join lua command executor")???;
|
||||||
|
info!("Command returned: `{}`", output);
|
||||||
|
|
||||||
|
Ok(EventStatus::Ok)
|
||||||
|
}
|
|
@ -1,9 +1,13 @@
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{command, command::Command, events::event_types::EventStatus, App},
|
app::{
|
||||||
ui,
|
command_interface::Command,
|
||||||
|
events::event_types::{Event, EventStatus},
|
||||||
|
App,
|
||||||
|
},
|
||||||
|
ui::central,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<EventStatus> {
|
pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<EventStatus> {
|
||||||
|
@ -11,38 +15,47 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
|
||||||
CrosstermEvent::Key(KeyEvent {
|
CrosstermEvent::Key(KeyEvent {
|
||||||
code: KeyCode::Esc, ..
|
code: KeyCode::Esc, ..
|
||||||
}) => {
|
}) => {
|
||||||
command::execute(app.channel_tx(), Command::Exit).await?;
|
app.tx
|
||||||
|
.send(Event::CommandEvent(Command::Exit, None))
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
CrosstermEvent::Key(KeyEvent {
|
CrosstermEvent::Key(KeyEvent {
|
||||||
code: KeyCode::Tab, ..
|
code: KeyCode::Tab, ..
|
||||||
}) => {
|
}) => {
|
||||||
command::execute(app.channel_tx(), Command::CyclePlanes).await?;
|
app.tx
|
||||||
|
.send(Event::CommandEvent(Command::CyclePlanes, None))
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
CrosstermEvent::Key(KeyEvent {
|
CrosstermEvent::Key(KeyEvent {
|
||||||
code: KeyCode::BackTab,
|
code: KeyCode::BackTab,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
command::execute(app.channel_tx(), Command::CyclePlanesRev).await?;
|
app.tx
|
||||||
|
.send(Event::CommandEvent(Command::CyclePlanesRev, None))
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
CrosstermEvent::Key(KeyEvent {
|
CrosstermEvent::Key(KeyEvent {
|
||||||
code: KeyCode::Char('c'),
|
code: KeyCode::Char('c'),
|
||||||
modifiers: KeyModifiers::CONTROL,
|
modifiers: KeyModifiers::CONTROL,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
command::execute(app.channel_tx(), Command::CommandLineShow).await?;
|
app.tx
|
||||||
|
.send(Event::CommandEvent(Command::CommandLineShow, None))
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
input => match app.ui.input_position() {
|
input => match app.ui.input_position() {
|
||||||
ui::MainInputPosition::MessageCompose => {
|
central::InputPosition::MessageCompose => {
|
||||||
match input {
|
match input {
|
||||||
CrosstermEvent::Key(KeyEvent {
|
CrosstermEvent::Key(KeyEvent {
|
||||||
code: KeyCode::Enter,
|
code: KeyCode::Enter,
|
||||||
modifiers: KeyModifiers::ALT,
|
modifiers: KeyModifiers::ALT,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
command::execute(
|
app.tx
|
||||||
app.channel_tx(),
|
.send(Event::CommandEvent(
|
||||||
Command::RoomMessageSend(app.ui.message_compose.lines().join("\n")),
|
Command::RoomMessageSend(app.ui.message_compose.lines().join("\n")),
|
||||||
)
|
None,
|
||||||
|
))
|
||||||
.await?;
|
.await?;
|
||||||
app.ui.message_compose_clear();
|
app.ui.message_compose_clear();
|
||||||
}
|
}
|
||||||
|
@ -53,7 +66,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
ui::MainInputPosition::Rooms => {
|
central::InputPosition::Rooms => {
|
||||||
match input {
|
match input {
|
||||||
CrosstermEvent::Key(KeyEvent {
|
CrosstermEvent::Key(KeyEvent {
|
||||||
code: KeyCode::Up, ..
|
code: KeyCode::Up, ..
|
||||||
|
@ -91,7 +104,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
ui::MainInputPosition::Messages => {
|
central::InputPosition::Messages => {
|
||||||
match input {
|
match input {
|
||||||
CrosstermEvent::Key(KeyEvent {
|
CrosstermEvent::Key(KeyEvent {
|
||||||
code: KeyCode::Up, ..
|
code: KeyCode::Up, ..
|
||||||
|
@ -136,14 +149,14 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
ui::MainInputPosition::CLI => {
|
central::InputPosition::CLI => {
|
||||||
if let Some(_) = app.ui.cli {
|
if let Some(_) = app.ui.cli {
|
||||||
match input {
|
match input {
|
||||||
CrosstermEvent::Key(KeyEvent {
|
CrosstermEvent::Key(KeyEvent {
|
||||||
code: KeyCode::Enter,
|
code: KeyCode::Enter,
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
let cli_event = app.ui
|
let ci_event = app.ui
|
||||||
.cli
|
.cli
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.expect("This is already checked")
|
.expect("This is already checked")
|
||||||
|
@ -153,16 +166,10 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
|
||||||
"There can only be one line in the buffer, as we collect it on enter being inputted"
|
"There can only be one line in the buffer, as we collect it on enter being inputted"
|
||||||
)
|
)
|
||||||
.to_owned();
|
.to_owned();
|
||||||
let output = app.handle_ci_event(&cli_event).await?;
|
app.tx
|
||||||
|
.send(Event::LuaCommand(ci_event))
|
||||||
// delete the old text:
|
.await
|
||||||
|
.context("Failed to send lua command to internal event stream")?;
|
||||||
// We can use a mutable borrow now, as we should only need one
|
|
||||||
let cli = app.ui.cli.as_mut().expect("Checked above");
|
|
||||||
cli.move_cursor(tui_textarea::CursorMove::Jump(0, 0));
|
|
||||||
cli.delete_str(0, cli_event.chars().count());
|
|
||||||
assert!(cli.is_empty());
|
|
||||||
cli.insert_str(output);
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
app.ui
|
app.ui
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
pub mod command;
|
// input events
|
||||||
pub mod main;
|
|
||||||
pub mod matrix;
|
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
|
pub mod main;
|
||||||
|
|
||||||
|
// matrix
|
||||||
|
pub mod matrix;
|
||||||
|
|
||||||
|
// ci
|
||||||
|
pub mod ci_output;
|
||||||
|
pub mod command;
|
||||||
|
pub mod lua_command;
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent};
|
use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent};
|
||||||
|
|
||||||
use crate::{
|
use crate::{app::{events::event_types::EventStatus, App}, ui::setup};
|
||||||
app::{events::event_types::EventStatus, App},
|
|
||||||
ui,
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -32,7 +29,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
match ui.input_position() {
|
match ui.input_position() {
|
||||||
ui::SetupInputPosition::Ok => {
|
setup::InputPosition::Ok => {
|
||||||
let homeserver = ui.homeserver.lines()[0].clone();
|
let homeserver = ui.homeserver.lines()[0].clone();
|
||||||
let username = ui.username.lines()[0].clone();
|
let username = ui.username.lines()[0].clone();
|
||||||
let password = ui.password_data.lines()[0].clone();
|
let password = ui.password_data.lines()[0].clone();
|
||||||
|
@ -46,13 +43,13 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
input => match ui.input_position() {
|
input => match ui.input_position() {
|
||||||
ui::SetupInputPosition::Homeserver => {
|
setup::InputPosition::Homeserver => {
|
||||||
ui.homeserver.input(input.to_owned());
|
ui.homeserver.input(input.to_owned());
|
||||||
}
|
}
|
||||||
ui::SetupInputPosition::Username => {
|
setup::InputPosition::Username => {
|
||||||
ui.username.input(input.to_owned());
|
ui.username.input(input.to_owned());
|
||||||
}
|
}
|
||||||
ui::SetupInputPosition::Password => {
|
setup::InputPosition::Password => {
|
||||||
let textarea_input = tui_textarea::Input::from(input.to_owned());
|
let textarea_input = tui_textarea::Input::from(input.to_owned());
|
||||||
ui.password_data.input(textarea_input.clone());
|
ui.password_data.input(textarea_input.clone());
|
||||||
match textarea_input.key {
|
match textarea_input.key {
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
mod handlers;
|
mod handlers;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use cli_log::{info, trace};
|
||||||
use crossterm::event::Event as CrosstermEvent;
|
use crossterm::event::Event as CrosstermEvent;
|
||||||
|
use tokio::sync::mpsc::Sender;
|
||||||
|
|
||||||
use crate::app::{command::Command, status::State, App};
|
use crate::app::{command_interface::Command, status::State, App};
|
||||||
|
|
||||||
use self::handlers::{command, main, matrix, setup};
|
use self::handlers::{command, lua_command, main, matrix, setup};
|
||||||
|
|
||||||
use super::EventStatus;
|
use super::EventStatus;
|
||||||
|
|
||||||
|
@ -13,22 +15,39 @@ use super::EventStatus;
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
InputEvent(CrosstermEvent),
|
InputEvent(CrosstermEvent),
|
||||||
MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse),
|
MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse),
|
||||||
CommandEvent(Command),
|
CommandEvent(Command, Option<Sender<String>>),
|
||||||
|
LuaCommand(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
pub async fn handle(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
pub async fn handle(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
||||||
|
trace!("Recieved event to handle: `{:#?}`", &self);
|
||||||
match &self {
|
match &self {
|
||||||
Event::MatrixEvent(event) => matrix::handle(app, event)
|
Event::MatrixEvent(event) => matrix::handle(app, event)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to handle matrix event: `{:#?}`", event)),
|
.with_context(|| format!("Failed to handle matrix event: `{:#?}`", event)),
|
||||||
|
|
||||||
Event::CommandEvent(event) => command::handle(app, event)
|
Event::CommandEvent(event, callback_tx) => {
|
||||||
|
let (result, output) = command::handle(app, event, callback_tx.is_some())
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to handle command event: `{:#?}`", event)),
|
.with_context(|| format!("Failed to handle command event: `{:#?}`", event))?;
|
||||||
|
|
||||||
|
if let Some(callback_tx) = callback_tx {
|
||||||
|
callback_tx
|
||||||
|
.send(output.clone())
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to send command output: {}", output))?;
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
Event::LuaCommand(lua_code) => lua_command::handle(app, lua_code.to_owned())
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to handle lua code: `{:#?}`", lua_code)),
|
||||||
|
|
||||||
Event::InputEvent(event) => match app.status.state() {
|
Event::InputEvent(event) => match app.status.state() {
|
||||||
State::None => Ok(EventStatus::Ok),
|
State::None => unreachable!(
|
||||||
|
"This state should not be available, when we are in the input handling"
|
||||||
|
),
|
||||||
State::Main => main::handle(app, event)
|
State::Main => main::handle(app, event)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to handle input event: `{:#?}`", event)),
|
.with_context(|| format!("Failed to handle input event: `{:#?}`", event)),
|
||||||
|
|
|
@ -1,45 +1,46 @@
|
||||||
pub mod command;
|
|
||||||
pub mod command_interface;
|
pub mod command_interface;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::{path::Path, sync::Arc};
|
||||||
|
|
||||||
use accounts::{Account, AccountsManager};
|
|
||||||
use anyhow::{Context, Error, Result};
|
use anyhow::{Context, Error, Result};
|
||||||
use cli_log::info;
|
use cli_log::info;
|
||||||
use matrix_sdk::Client;
|
use matrix_sdk::Client;
|
||||||
use rlua::Lua;
|
use mlua::Lua;
|
||||||
use status::{State, Status};
|
use status::{State, Status};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::{accounts, app::command_interface::generate_ci_functions, ui};
|
use crate::{
|
||||||
|
accounts::{Account, AccountsManager},
|
||||||
|
app::{command_interface::generate_ci_functions, events::event_types::Event},
|
||||||
|
ui::{central, setup},
|
||||||
|
};
|
||||||
|
|
||||||
use self::events::event_types::{self, Event};
|
use self::events::event_types;
|
||||||
|
|
||||||
pub struct App<'ui> {
|
pub struct App<'ui> {
|
||||||
ui: ui::UI<'ui>,
|
ui: central::UI<'ui>,
|
||||||
accounts_manager: accounts::AccountsManager,
|
accounts_manager: AccountsManager,
|
||||||
status: Status,
|
status: Status,
|
||||||
|
|
||||||
channel_tx: mpsc::Sender<event_types::Event>,
|
tx: mpsc::Sender<Event>,
|
||||||
channel_rx: mpsc::Receiver<event_types::Event>,
|
rx: mpsc::Receiver<Event>,
|
||||||
|
|
||||||
input_listener_killer: CancellationToken,
|
input_listener_killer: CancellationToken,
|
||||||
matrix_listener_killer: CancellationToken,
|
matrix_listener_killer: CancellationToken,
|
||||||
|
|
||||||
lua: Lua,
|
lua: Arc<Lua>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App<'_> {
|
impl App<'_> {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
fn set_up_lua() -> Lua {
|
fn set_up_lua(tx: mpsc::Sender<Event>) -> Arc<Lua> {
|
||||||
let lua = Lua::new();
|
let mut lua = Lua::new();
|
||||||
|
|
||||||
lua.context(|mut lua_context| {
|
generate_ci_functions(&mut lua, tx);
|
||||||
generate_ci_functions(&mut lua_context);
|
Arc::new(lua)
|
||||||
});
|
|
||||||
lua
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let path: &std::path::Path = Path::new("userdata/accounts.json");
|
let path: &std::path::Path = Path::new("userdata/accounts.json");
|
||||||
|
@ -50,41 +51,25 @@ impl App<'_> {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let (channel_tx, channel_rx) = mpsc::channel(256);
|
let (tx, rx) = mpsc::channel(256);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ui: ui::UI::new()?,
|
ui: central::UI::new()?,
|
||||||
accounts_manager: AccountsManager::new(config)?,
|
accounts_manager: AccountsManager::new(config)?,
|
||||||
status: Status::new(None),
|
status: Status::new(None),
|
||||||
|
|
||||||
channel_tx,
|
tx: tx.clone(),
|
||||||
channel_rx,
|
rx,
|
||||||
input_listener_killer: CancellationToken::new(),
|
input_listener_killer: CancellationToken::new(),
|
||||||
matrix_listener_killer: CancellationToken::new(),
|
matrix_listener_killer: CancellationToken::new(),
|
||||||
|
|
||||||
lua: set_up_lua(),
|
lua: set_up_lua(tx),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_ci_event(&self, event: &str) -> Result<String> {
|
|
||||||
info!("Recieved ci event: `{event}`; executing..");
|
|
||||||
|
|
||||||
// TODO: Should the ci support more than strings?
|
|
||||||
let output = self.lua.context(|context| -> Result<String> {
|
|
||||||
let output = context
|
|
||||||
.load(&event)
|
|
||||||
.eval::<String>()
|
|
||||||
.with_context(|| format!("Failed to execute: `{event}`"))?;
|
|
||||||
info!("Function evaluated to: `{output}`");
|
|
||||||
Ok(output)
|
|
||||||
})?;
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn run(&mut self) -> Result<()> {
|
pub async fn run(&mut self) -> Result<()> {
|
||||||
// Spawn input event listener
|
// Spawn input event listener
|
||||||
tokio::task::spawn(events::poll_input_events(
|
tokio::task::spawn(events::poll_input_events(
|
||||||
self.channel_tx.clone(),
|
self.tx.clone(),
|
||||||
self.input_listener_killer.clone(),
|
self.input_listener_killer.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -100,10 +85,7 @@ impl App<'_> {
|
||||||
self.status.set_state(State::Main);
|
self.status.set_state(State::Main);
|
||||||
self.ui.update(&self.status).await?;
|
self.ui.update(&self.status).await?;
|
||||||
|
|
||||||
let event: event_types::Event = match self.channel_rx.recv().await {
|
let event = self.rx.recv().await.context("Failed to get next event")?;
|
||||||
Some(e) => e,
|
|
||||||
None => return Err(Error::msg("Event channel has no senders")),
|
|
||||||
};
|
|
||||||
|
|
||||||
match event.handle(self).await? {
|
match event.handle(self).await? {
|
||||||
event_types::EventStatus::Ok => (),
|
event_types::EventStatus::Ok => (),
|
||||||
|
@ -117,16 +99,13 @@ impl App<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup(&mut self) -> Result<()> {
|
async fn setup(&mut self) -> Result<()> {
|
||||||
self.ui.setup_ui = Some(ui::SetupUI::new());
|
self.ui.setup_ui = Some(setup::UI::new());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
self.status.set_state(State::Setup);
|
self.status.set_state(State::Setup);
|
||||||
self.ui.update_setup().await?;
|
self.ui.update_setup().await?;
|
||||||
|
|
||||||
let event: event_types::Event = match self.channel_rx.recv().await {
|
let event = self.rx.recv().await.context("Failed to get next event")?;
|
||||||
Some(e) => e,
|
|
||||||
None => return Err(Error::msg("Event channel has no senders")),
|
|
||||||
};
|
|
||||||
|
|
||||||
match event.handle(self).await? {
|
match event.handle(self).await? {
|
||||||
event_types::EventStatus::Ok => (),
|
event_types::EventStatus::Ok => (),
|
||||||
|
@ -150,7 +129,7 @@ impl App<'_> {
|
||||||
|
|
||||||
// Spawn Matrix Event Listener
|
// Spawn Matrix Event Listener
|
||||||
tokio::task::spawn(events::poll_matrix_events(
|
tokio::task::spawn(events::poll_matrix_events(
|
||||||
self.channel_tx.clone(),
|
self.tx.clone(),
|
||||||
self.matrix_listener_killer.clone(),
|
self.matrix_listener_killer.clone(),
|
||||||
client.clone(),
|
client.clone(),
|
||||||
));
|
));
|
||||||
|
@ -204,8 +183,4 @@ impl App<'_> {
|
||||||
pub fn client(&self) -> Option<&Client> {
|
pub fn client(&self) -> Option<&Client> {
|
||||||
self.accounts_manager.client()
|
self.accounts_manager.client()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channel_tx(&self) -> &mpsc::Sender<Event> {
|
|
||||||
&self.channel_tx
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use cli_log::{warn, info};
|
use cli_log::warn;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
room::MessagesOptions,
|
room::MessagesOptions,
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
pub mod update;
|
||||||
|
|
||||||
|
use std::io::Stdout;
|
||||||
|
|
||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use cli_log::{info, warn};
|
||||||
|
use crossterm::{
|
||||||
|
event::DisableMouseCapture,
|
||||||
|
execute,
|
||||||
|
terminal::{disable_raw_mode, LeaveAlternateScreen},
|
||||||
|
};
|
||||||
|
use tui::{
|
||||||
|
backend::CrosstermBackend,
|
||||||
|
widgets::{Block, Borders, ListState},
|
||||||
|
Terminal,
|
||||||
|
};
|
||||||
|
use tui_textarea::TextArea;
|
||||||
|
|
||||||
|
use crate::ui::terminal_prepare;
|
||||||
|
|
||||||
|
use super::setup;
|
||||||
|
|
||||||
|
pub use update::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub enum InputPosition {
|
||||||
|
Status,
|
||||||
|
Rooms,
|
||||||
|
Messages,
|
||||||
|
MessageCompose,
|
||||||
|
RoomInfo,
|
||||||
|
CLI,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UI<'a> {
|
||||||
|
terminal: Terminal<CrosstermBackend<Stdout>>,
|
||||||
|
input_position: InputPosition,
|
||||||
|
pub rooms_state: ListState,
|
||||||
|
pub message_compose: TextArea<'a>,
|
||||||
|
pub cli: Option<TextArea<'a>>,
|
||||||
|
|
||||||
|
pub setup_ui: Option<setup::UI<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for UI<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
info!("Destructing UI");
|
||||||
|
disable_raw_mode().expect("While destructing UI -> Failed to disable raw mode");
|
||||||
|
execute!(
|
||||||
|
self.terminal.backend_mut(),
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
DisableMouseCapture
|
||||||
|
).expect("While destructing UI -> Failed execute backend commands (LeaveAlternateScreen and DisableMouseCapture)");
|
||||||
|
self.terminal
|
||||||
|
.show_cursor()
|
||||||
|
.expect("While destructing UI -> Failed to re-enable cursor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UI<'_> {
|
||||||
|
pub fn new() -> Result<Self> {
|
||||||
|
let stdout = terminal_prepare().context("Falied to prepare terminal")?;
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
|
terminal.clear()?;
|
||||||
|
|
||||||
|
let mut message_compose = TextArea::default();
|
||||||
|
message_compose.set_block(
|
||||||
|
Block::default()
|
||||||
|
.title("Message Compose (send: <Alt>+<Enter>)")
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("Initialized UI");
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
terminal,
|
||||||
|
input_position: InputPosition::Rooms,
|
||||||
|
rooms_state: ListState::default(),
|
||||||
|
message_compose,
|
||||||
|
cli: None,
|
||||||
|
setup_ui: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cycle_main_input_position(&mut self) {
|
||||||
|
self.input_position = match self.input_position {
|
||||||
|
InputPosition::Status => InputPosition::Rooms,
|
||||||
|
InputPosition::Rooms => InputPosition::Messages,
|
||||||
|
InputPosition::Messages => InputPosition::MessageCompose,
|
||||||
|
InputPosition::MessageCompose => InputPosition::RoomInfo,
|
||||||
|
InputPosition::RoomInfo => match self.cli {
|
||||||
|
Some(_) => InputPosition::CLI,
|
||||||
|
None => InputPosition::Status,
|
||||||
|
},
|
||||||
|
InputPosition::CLI => InputPosition::Status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cycle_main_input_position_rev(&mut self) {
|
||||||
|
self.input_position = match self.input_position {
|
||||||
|
InputPosition::Status => match self.cli {
|
||||||
|
Some(_) => InputPosition::CLI,
|
||||||
|
None => InputPosition::RoomInfo,
|
||||||
|
},
|
||||||
|
InputPosition::Rooms => InputPosition::Status,
|
||||||
|
InputPosition::Messages => InputPosition::Rooms,
|
||||||
|
InputPosition::MessageCompose => InputPosition::Messages,
|
||||||
|
InputPosition::RoomInfo => InputPosition::MessageCompose,
|
||||||
|
InputPosition::CLI => InputPosition::RoomInfo,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn input_position(&self) -> &InputPosition {
|
||||||
|
&self.input_position
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn message_compose_clear(&mut self) {
|
||||||
|
self.message_compose = TextArea::default();
|
||||||
|
self.message_compose.set_block(
|
||||||
|
Block::default()
|
||||||
|
.title("Message Compose (send: <Alt>+<Enter>)")
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_command_output(&mut self, output: &str) {
|
||||||
|
info!("Setting output to: `{}`", output);
|
||||||
|
if let Some(_) = self.cli {
|
||||||
|
let cli = Some(TextArea::from([output]));
|
||||||
|
self.cli = cli;
|
||||||
|
} else {
|
||||||
|
warn!("Failed to set output");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cli_enable(&mut self) {
|
||||||
|
self.input_position = InputPosition::CLI;
|
||||||
|
if self.cli.is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut cli = TextArea::default();
|
||||||
|
cli.set_block(Block::default().borders(Borders::ALL));
|
||||||
|
self.cli = Some(cli);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cli_disable(&mut self) {
|
||||||
|
if self.input_position == InputPosition::CLI {
|
||||||
|
self.cycle_main_input_position();
|
||||||
|
}
|
||||||
|
self.cli = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_setup(&mut self) -> Result<()> {
|
||||||
|
let ui = match &mut self.setup_ui {
|
||||||
|
Some(c) => c,
|
||||||
|
None => bail!("SetupUI instance not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
ui.update(&mut self.terminal).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
use std::cmp;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use tui::{
|
||||||
|
layout::{Constraint, Direction, Layout},
|
||||||
|
style::Color,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::status::Status,
|
||||||
|
ui::{textarea_activate, textarea_inactivate},
|
||||||
|
};
|
||||||
|
|
||||||
|
use self::widgets::{messages, room_info, rooms, status};
|
||||||
|
|
||||||
|
use super::{InputPosition, UI};
|
||||||
|
|
||||||
|
pub mod widgets;
|
||||||
|
|
||||||
|
impl UI<'_> {
|
||||||
|
pub async fn update(&mut self, status: &Status) -> Result<()> {
|
||||||
|
let chunks = match self.cli {
|
||||||
|
Some(_) => Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Min(10), Constraint::Length(3)].as_ref())
|
||||||
|
.split(self.terminal.size()?),
|
||||||
|
None => vec![self.terminal.size()?],
|
||||||
|
};
|
||||||
|
|
||||||
|
let main_chunks = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length(32),
|
||||||
|
Constraint::Min(16),
|
||||||
|
Constraint::Length(32),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(chunks[0]);
|
||||||
|
|
||||||
|
let left_chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Length(5), Constraint::Min(4)].as_ref())
|
||||||
|
.split(main_chunks[0]);
|
||||||
|
|
||||||
|
let middle_chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Min(4),
|
||||||
|
Constraint::Length(cmp::min(2 + self.message_compose.lines().len() as u16, 8)),
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(main_chunks[1]);
|
||||||
|
|
||||||
|
let right_chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Min(4)].as_ref())
|
||||||
|
.split(main_chunks[2]);
|
||||||
|
|
||||||
|
// calculate to widgets colors, based of which widget is currently selected
|
||||||
|
let colors = match self.input_position {
|
||||||
|
InputPosition::Status => {
|
||||||
|
textarea_inactivate(&mut self.message_compose);
|
||||||
|
if let Some(cli) = &mut self.cli {
|
||||||
|
textarea_inactivate(cli);
|
||||||
|
}
|
||||||
|
vec![
|
||||||
|
Color::White,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
InputPosition::Rooms => {
|
||||||
|
textarea_inactivate(&mut self.message_compose);
|
||||||
|
if let Some(cli) = &mut self.cli {
|
||||||
|
textarea_inactivate(cli);
|
||||||
|
}
|
||||||
|
vec![
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::White,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
InputPosition::Messages => {
|
||||||
|
textarea_inactivate(&mut self.message_compose);
|
||||||
|
if let Some(cli) = &mut self.cli {
|
||||||
|
textarea_inactivate(cli);
|
||||||
|
}
|
||||||
|
vec![
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::White,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
InputPosition::MessageCompose => {
|
||||||
|
textarea_activate(&mut self.message_compose);
|
||||||
|
if let Some(cli) = &mut self.cli {
|
||||||
|
textarea_inactivate(cli);
|
||||||
|
}
|
||||||
|
vec![
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
InputPosition::RoomInfo => {
|
||||||
|
textarea_inactivate(&mut self.message_compose);
|
||||||
|
if let Some(cli) = &mut self.cli {
|
||||||
|
textarea_inactivate(cli);
|
||||||
|
}
|
||||||
|
vec![
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::White,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
InputPosition::CLI => {
|
||||||
|
textarea_inactivate(&mut self.message_compose);
|
||||||
|
if let Some(cli) = &mut self.cli {
|
||||||
|
textarea_activate(cli);
|
||||||
|
}
|
||||||
|
vec![
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
Color::DarkGray,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// initiate the widgets
|
||||||
|
let status_panel = status::init(status, &colors);
|
||||||
|
let rooms_panel = rooms::init(status, &colors);
|
||||||
|
let (messages_panel, mut messages_state) = messages::init(status.room(), &colors)
|
||||||
|
.context("Failed to initiate the messages widget")?;
|
||||||
|
let room_info_panel = room_info::init(status.room(), &colors);
|
||||||
|
|
||||||
|
// render the widgets
|
||||||
|
self.terminal.draw(|frame| {
|
||||||
|
frame.render_widget(status_panel, left_chunks[0]);
|
||||||
|
frame.render_stateful_widget(rooms_panel, left_chunks[1], &mut self.rooms_state);
|
||||||
|
frame.render_stateful_widget(messages_panel, middle_chunks[0], &mut messages_state);
|
||||||
|
frame.render_widget(self.message_compose.widget(), middle_chunks[1]);
|
||||||
|
match &self.cli {
|
||||||
|
Some(cli) => frame.render_widget(cli.widget(), chunks[1]),
|
||||||
|
None => (),
|
||||||
|
};
|
||||||
|
frame.render_widget(room_info_panel, right_chunks[0]);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use matrix_sdk::ruma::events::{AnyMessageLikeEvent, AnyTimelineEvent};
|
||||||
|
use tui::{
|
||||||
|
layout::Corner,
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
text::{Span, Spans, Text},
|
||||||
|
widgets::{Block, Borders, List, ListItem, ListState},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{app::status::Room, ui::central::InputPosition};
|
||||||
|
|
||||||
|
pub fn init<'a>(room: Option<&'a Room>, colors: &Vec<Color>) -> Result<(List<'a>, ListState)> {
|
||||||
|
let content = match room {
|
||||||
|
Some(room) => get_content_from_room(room).context("Failed to get content from room")?,
|
||||||
|
None => vec![ListItem::new(Text::styled(
|
||||||
|
"No room selected!",
|
||||||
|
Style::default().fg(Color::Red),
|
||||||
|
))],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut messages_state = ListState::default();
|
||||||
|
|
||||||
|
if let Some(room) = room {
|
||||||
|
messages_state.select(room.view_scroll());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
List::new(content)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title("Messages")
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(colors[InputPosition::Messages as usize])),
|
||||||
|
)
|
||||||
|
.start_corner(Corner::BottomLeft)
|
||||||
|
.highlight_symbol(">")
|
||||||
|
.highlight_style(
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::LightMagenta)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
),
|
||||||
|
messages_state,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_content_from_room(room: &Room) -> Result<Vec<ListItem>> {
|
||||||
|
let results: Vec<Result<ListItem>> = room
|
||||||
|
.timeline()
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.map(|event| filter_event(event).context("Failed to filter event"))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut output = Vec::with_capacity(results.len());
|
||||||
|
for result in results {
|
||||||
|
output.push(result?);
|
||||||
|
}
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_event<'a>(event: &AnyTimelineEvent) -> Result<ListItem<'a>> {
|
||||||
|
match event {
|
||||||
|
// Message Like Events
|
||||||
|
AnyTimelineEvent::MessageLike(message_like_event) => {
|
||||||
|
let (content, color) = match &message_like_event {
|
||||||
|
AnyMessageLikeEvent::RoomMessage(room_message_event) => {
|
||||||
|
let message_content = &room_message_event
|
||||||
|
.as_original()
|
||||||
|
.context("Failed to get inner original message_event")?
|
||||||
|
.content
|
||||||
|
.body();
|
||||||
|
|
||||||
|
(message_content.to_string(), Color::White)
|
||||||
|
}
|
||||||
|
_ => (
|
||||||
|
format!(
|
||||||
|
"~~~ not supported message like event: {} ~~~",
|
||||||
|
message_like_event.event_type().to_string()
|
||||||
|
),
|
||||||
|
Color::Red,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let mut text = Text::styled(
|
||||||
|
message_like_event.sender().to_string(),
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::Cyan)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
);
|
||||||
|
text.extend(Text::styled(
|
||||||
|
content.to_string(),
|
||||||
|
Style::default().fg(color),
|
||||||
|
));
|
||||||
|
Ok(ListItem::new(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
// State Events
|
||||||
|
AnyTimelineEvent::State(state) => Ok(ListItem::new(vec![Spans::from(vec![
|
||||||
|
Span::styled(
|
||||||
|
state.sender().to_string(),
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
),
|
||||||
|
Span::styled(": ", Style::default().fg(Color::DarkGray)),
|
||||||
|
Span::styled(
|
||||||
|
state.event_type().to_string(),
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
),
|
||||||
|
])])),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod messages;
|
||||||
|
pub mod room_info;
|
||||||
|
pub mod rooms;
|
||||||
|
pub mod status;
|
|
@ -0,0 +1,36 @@
|
||||||
|
use tui::{
|
||||||
|
style::{Color, Style},
|
||||||
|
text::Text,
|
||||||
|
widgets::{Block, Borders, Paragraph}, layout::Alignment,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{app::status::Room, ui::central::InputPosition};
|
||||||
|
|
||||||
|
pub fn init<'a>(room: Option<&'a Room>, colors: &Vec<Color>) -> Paragraph<'a> {
|
||||||
|
let mut room_info_content = Text::default();
|
||||||
|
if let Some(room) = room {
|
||||||
|
room_info_content.extend(Text::styled(room.name(), Style::default().fg(Color::Cyan)));
|
||||||
|
if room.encrypted() {
|
||||||
|
room_info_content.extend(Text::styled("Encrypted", Style::default().fg(Color::Green)));
|
||||||
|
} else {
|
||||||
|
room_info_content.extend(Text::styled(
|
||||||
|
"Not Encrypted!",
|
||||||
|
Style::default().fg(Color::Red),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
room_info_content.extend(Text::styled(
|
||||||
|
"No room selected!",
|
||||||
|
Style::default().fg(Color::Red),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Paragraph::new(room_info_content)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title("Room Info")
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(colors[InputPosition::RoomInfo as usize])),
|
||||||
|
)
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
use tui::{
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
text::Span,
|
||||||
|
widgets::{Borders, List, ListItem, Block},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{app::status::Status, ui::central::InputPosition};
|
||||||
|
|
||||||
|
pub fn init<'a>(status: &'a Status, colors: &Vec<Color>) -> List<'a> {
|
||||||
|
let rooms_content: Vec<_> = status
|
||||||
|
.rooms()
|
||||||
|
.iter()
|
||||||
|
.map(|(_, room)| ListItem::new(Span::styled(room.name(), Style::default())))
|
||||||
|
.collect();
|
||||||
|
List::new(rooms_content)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title("Rooms (navigate: arrow keys)")
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(colors[InputPosition::Rooms as usize])),
|
||||||
|
)
|
||||||
|
.style(Style::default().fg(Color::DarkGray))
|
||||||
|
.highlight_style(
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::Cyan)
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
)
|
||||||
|
.highlight_symbol(">")
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
use tui::{
|
||||||
|
layout::Alignment,
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
text::Text,
|
||||||
|
widgets::{Block, Borders, Paragraph},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{app::status::Status, ui::central::InputPosition};
|
||||||
|
|
||||||
|
pub fn init<'a>(status: &'a Status, colors: &Vec<Color>) -> Paragraph<'a> {
|
||||||
|
let mut status_content = Text::styled(
|
||||||
|
status.account_name(),
|
||||||
|
Style::default().add_modifier(Modifier::BOLD),
|
||||||
|
);
|
||||||
|
status_content.extend(Text::styled(status.account_user_id(), Style::default()));
|
||||||
|
status_content.extend(Text::styled(
|
||||||
|
"settings",
|
||||||
|
Style::default()
|
||||||
|
.fg(Color::LightMagenta)
|
||||||
|
.add_modifier(Modifier::ITALIC | Modifier::UNDERLINED),
|
||||||
|
));
|
||||||
|
Paragraph::new(status_content)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title("Status")
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.style(Style::default().fg(colors[InputPosition::Status as usize])),
|
||||||
|
)
|
||||||
|
.alignment(Alignment::Left)
|
||||||
|
}
|
609
src/ui/mod.rs
609
src/ui/mod.rs
|
@ -1,64 +1,23 @@
|
||||||
use std::{cmp, io, io::Stdout};
|
pub mod central;
|
||||||
|
pub mod setup;
|
||||||
|
|
||||||
use anyhow::{Error, Result};
|
use std::{io, io::Stdout};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
use cli_log::info;
|
use cli_log::info;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{DisableMouseCapture, EnableMouseCapture},
|
event::EnableMouseCapture,
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{enable_raw_mode, EnterAlternateScreen},
|
||||||
};
|
};
|
||||||
use matrix_sdk::ruma::events::{AnyMessageLikeEvent, AnyTimelineEvent};
|
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::CrosstermBackend,
|
|
||||||
layout::{Alignment, Constraint, Corner, Direction, Layout},
|
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
text::{Span, Spans, Text},
|
widgets::{Block, Borders},
|
||||||
widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
|
|
||||||
Terminal,
|
|
||||||
};
|
};
|
||||||
use tui_textarea::TextArea;
|
use tui_textarea::TextArea;
|
||||||
|
|
||||||
use crate::app::status::Status;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum SetupInputPosition {
|
|
||||||
Homeserver,
|
|
||||||
Username,
|
|
||||||
Password,
|
|
||||||
Ok,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
|
||||||
pub enum MainInputPosition {
|
|
||||||
Status,
|
|
||||||
Rooms,
|
|
||||||
Messages,
|
|
||||||
MessageCompose,
|
|
||||||
RoomInfo,
|
|
||||||
CLI,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SetupUI<'a> {
|
|
||||||
input_position: SetupInputPosition,
|
|
||||||
|
|
||||||
pub homeserver: TextArea<'a>,
|
|
||||||
pub username: TextArea<'a>,
|
|
||||||
pub password: TextArea<'a>,
|
|
||||||
pub password_data: TextArea<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UI<'a> {
|
|
||||||
terminal: Terminal<CrosstermBackend<Stdout>>,
|
|
||||||
input_position: MainInputPosition,
|
|
||||||
pub rooms_state: ListState,
|
|
||||||
pub message_compose: TextArea<'a>,
|
|
||||||
pub cli: Option<TextArea<'a>>,
|
|
||||||
|
|
||||||
pub setup_ui: Option<SetupUI<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn terminal_prepare() -> Result<Stdout> {
|
fn terminal_prepare() -> Result<Stdout> {
|
||||||
enable_raw_mode()?;
|
enable_raw_mode().context("Failed to enable raw mode")?;
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||||
info!("Prepared terminal");
|
info!("Prepared terminal");
|
||||||
|
@ -84,553 +43,3 @@ pub fn textarea_inactivate(textarea: &mut TextArea) {
|
||||||
.unwrap_or_else(|| Block::default().borders(Borders::ALL));
|
.unwrap_or_else(|| Block::default().borders(Borders::ALL));
|
||||||
textarea.set_block(b.style(Style::default().fg(Color::DarkGray)));
|
textarea.set_block(b.style(Style::default().fg(Color::DarkGray)));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for UI<'_> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
info!("Destructing UI");
|
|
||||||
disable_raw_mode().expect("While destructing UI -> Failed to disable raw mode");
|
|
||||||
execute!(
|
|
||||||
self.terminal.backend_mut(),
|
|
||||||
LeaveAlternateScreen,
|
|
||||||
DisableMouseCapture
|
|
||||||
).expect("While destructing UI -> Failed execute backend commands (LeaveAlternateScreen and DisableMouseCapture)");
|
|
||||||
self.terminal
|
|
||||||
.show_cursor()
|
|
||||||
.expect("While destructing UI -> Failed to re-enable cursor");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SetupUI<'_> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let mut homeserver = TextArea::new(vec!["https://matrix.org".to_string()]);
|
|
||||||
let mut username = TextArea::default();
|
|
||||||
let mut password = TextArea::default();
|
|
||||||
let password_data = TextArea::default();
|
|
||||||
|
|
||||||
homeserver.set_block(Block::default().title("Homeserver").borders(Borders::ALL));
|
|
||||||
username.set_block(Block::default().title("Username").borders(Borders::ALL));
|
|
||||||
password.set_block(Block::default().title("Password").borders(Borders::ALL));
|
|
||||||
|
|
||||||
textarea_activate(&mut homeserver);
|
|
||||||
textarea_inactivate(&mut username);
|
|
||||||
textarea_inactivate(&mut password);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
input_position: SetupInputPosition::Homeserver,
|
|
||||||
homeserver,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
password_data,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cycle_input_position(&mut self) {
|
|
||||||
self.input_position = match self.input_position {
|
|
||||||
SetupInputPosition::Homeserver => {
|
|
||||||
textarea_inactivate(&mut self.homeserver);
|
|
||||||
textarea_activate(&mut self.username);
|
|
||||||
textarea_inactivate(&mut self.password);
|
|
||||||
SetupInputPosition::Username
|
|
||||||
}
|
|
||||||
SetupInputPosition::Username => {
|
|
||||||
textarea_inactivate(&mut self.homeserver);
|
|
||||||
textarea_inactivate(&mut self.username);
|
|
||||||
textarea_activate(&mut self.password);
|
|
||||||
SetupInputPosition::Password
|
|
||||||
}
|
|
||||||
SetupInputPosition::Password => {
|
|
||||||
textarea_inactivate(&mut self.homeserver);
|
|
||||||
textarea_inactivate(&mut self.username);
|
|
||||||
textarea_inactivate(&mut self.password);
|
|
||||||
SetupInputPosition::Ok
|
|
||||||
}
|
|
||||||
SetupInputPosition::Ok => {
|
|
||||||
textarea_activate(&mut self.homeserver);
|
|
||||||
textarea_inactivate(&mut self.username);
|
|
||||||
textarea_inactivate(&mut self.password);
|
|
||||||
SetupInputPosition::Homeserver
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cycle_input_position_rev(&mut self) {
|
|
||||||
self.input_position = match self.input_position {
|
|
||||||
SetupInputPosition::Homeserver => {
|
|
||||||
textarea_inactivate(&mut self.homeserver);
|
|
||||||
textarea_inactivate(&mut self.username);
|
|
||||||
textarea_inactivate(&mut self.password);
|
|
||||||
SetupInputPosition::Ok
|
|
||||||
}
|
|
||||||
SetupInputPosition::Username => {
|
|
||||||
textarea_activate(&mut self.homeserver);
|
|
||||||
textarea_inactivate(&mut self.username);
|
|
||||||
textarea_inactivate(&mut self.password);
|
|
||||||
SetupInputPosition::Homeserver
|
|
||||||
}
|
|
||||||
SetupInputPosition::Password => {
|
|
||||||
textarea_inactivate(&mut self.homeserver);
|
|
||||||
textarea_activate(&mut self.username);
|
|
||||||
textarea_inactivate(&mut self.password);
|
|
||||||
SetupInputPosition::Username
|
|
||||||
}
|
|
||||||
SetupInputPosition::Ok => {
|
|
||||||
textarea_inactivate(&mut self.homeserver);
|
|
||||||
textarea_inactivate(&mut self.username);
|
|
||||||
textarea_activate(&mut self.password);
|
|
||||||
SetupInputPosition::Password
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn input_position(&self) -> &SetupInputPosition {
|
|
||||||
&self.input_position
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update(
|
|
||||||
&'_ mut self,
|
|
||||||
terminal: &mut Terminal<CrosstermBackend<Stdout>>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut strings: Vec<String> = Vec::new();
|
|
||||||
strings.resize(3, "".to_string());
|
|
||||||
|
|
||||||
let content_ok = match self.input_position {
|
|
||||||
SetupInputPosition::Ok => {
|
|
||||||
Span::styled("OK", Style::default().add_modifier(Modifier::UNDERLINED))
|
|
||||||
}
|
|
||||||
_ => Span::styled("OK", Style::default().fg(Color::DarkGray)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let block = Block::default().title("Login").borders(Borders::ALL);
|
|
||||||
|
|
||||||
let ok = Paragraph::new(content_ok).alignment(Alignment::Center);
|
|
||||||
|
|
||||||
// define a 32 * 6 chunk in the middle of the screen
|
|
||||||
let mut chunk = terminal.size()?;
|
|
||||||
chunk.x = (chunk.width / 2) - 16;
|
|
||||||
chunk.y = (chunk.height / 2) - 5;
|
|
||||||
chunk.height = 12;
|
|
||||||
chunk.width = 32;
|
|
||||||
|
|
||||||
let mut split_chunk = chunk.clone();
|
|
||||||
split_chunk.x += 1;
|
|
||||||
split_chunk.y += 1;
|
|
||||||
split_chunk.height -= 1;
|
|
||||||
split_chunk.width -= 2;
|
|
||||||
|
|
||||||
let chunks = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints(
|
|
||||||
[
|
|
||||||
Constraint::Length(3), // 0. Homserver:
|
|
||||||
Constraint::Length(3), // 1. Username:
|
|
||||||
Constraint::Length(3), // 2. Password:
|
|
||||||
Constraint::Length(1), // 3. OK
|
|
||||||
]
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(split_chunk);
|
|
||||||
|
|
||||||
terminal.draw(|frame| {
|
|
||||||
frame.render_widget(block.clone(), chunk);
|
|
||||||
frame.render_widget(self.homeserver.widget(), chunks[0]);
|
|
||||||
frame.render_widget(self.username.widget(), chunks[1]);
|
|
||||||
frame.render_widget(self.password.widget(), chunks[2]);
|
|
||||||
frame.render_widget(ok.clone(), chunks[3]);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UI<'_> {
|
|
||||||
pub fn new() -> Result<Self> {
|
|
||||||
let stdout = terminal_prepare()?;
|
|
||||||
let backend = CrosstermBackend::new(stdout);
|
|
||||||
let mut terminal = Terminal::new(backend)?;
|
|
||||||
|
|
||||||
terminal.clear()?;
|
|
||||||
|
|
||||||
let mut message_compose = TextArea::default();
|
|
||||||
message_compose.set_block(
|
|
||||||
Block::default()
|
|
||||||
.title("Message Compose (send: <Alt>+<Enter>)")
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
);
|
|
||||||
|
|
||||||
info!("Initialized UI");
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
terminal,
|
|
||||||
input_position: MainInputPosition::Rooms,
|
|
||||||
rooms_state: ListState::default(),
|
|
||||||
message_compose,
|
|
||||||
cli: None,
|
|
||||||
setup_ui: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cycle_main_input_position(&mut self) {
|
|
||||||
self.input_position = match self.input_position {
|
|
||||||
MainInputPosition::Status => MainInputPosition::Rooms,
|
|
||||||
MainInputPosition::Rooms => MainInputPosition::Messages,
|
|
||||||
MainInputPosition::Messages => MainInputPosition::MessageCompose,
|
|
||||||
MainInputPosition::MessageCompose => MainInputPosition::RoomInfo,
|
|
||||||
MainInputPosition::RoomInfo => match self.cli {
|
|
||||||
Some(_) => MainInputPosition::CLI,
|
|
||||||
None => MainInputPosition::Status,
|
|
||||||
},
|
|
||||||
MainInputPosition::CLI => MainInputPosition::Status,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cycle_main_input_position_rev(&mut self) {
|
|
||||||
self.input_position = match self.input_position {
|
|
||||||
MainInputPosition::Status => match self.cli {
|
|
||||||
Some(_) => MainInputPosition::CLI,
|
|
||||||
None => MainInputPosition::RoomInfo,
|
|
||||||
},
|
|
||||||
MainInputPosition::Rooms => MainInputPosition::Status,
|
|
||||||
MainInputPosition::Messages => MainInputPosition::Rooms,
|
|
||||||
MainInputPosition::MessageCompose => MainInputPosition::Messages,
|
|
||||||
MainInputPosition::RoomInfo => MainInputPosition::MessageCompose,
|
|
||||||
MainInputPosition::CLI => MainInputPosition::RoomInfo,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn input_position(&self) -> &MainInputPosition {
|
|
||||||
&self.input_position
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn message_compose_clear(&mut self) {
|
|
||||||
self.message_compose = TextArea::default();
|
|
||||||
self.message_compose.set_block(
|
|
||||||
Block::default()
|
|
||||||
.title("Message Compose (send: <Alt>+<Enter>)")
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cli_enable(&mut self) {
|
|
||||||
self.input_position = MainInputPosition::CLI;
|
|
||||||
if self.cli.is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut cli = TextArea::default();
|
|
||||||
cli.set_block(Block::default().borders(Borders::ALL));
|
|
||||||
self.cli = Some(cli);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cli_disable(&mut self) {
|
|
||||||
if self.input_position == MainInputPosition::CLI {
|
|
||||||
self.cycle_main_input_position();
|
|
||||||
}
|
|
||||||
self.cli = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update(&mut self, status: &Status) -> Result<()> {
|
|
||||||
let chunks = match self.cli {
|
|
||||||
Some(_) => Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints([Constraint::Min(10), Constraint::Length(3)].as_ref())
|
|
||||||
.split(self.terminal.size()?),
|
|
||||||
None => vec![self.terminal.size()?],
|
|
||||||
};
|
|
||||||
|
|
||||||
let main_chunks = Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints(
|
|
||||||
[
|
|
||||||
Constraint::Length(32),
|
|
||||||
Constraint::Min(16),
|
|
||||||
Constraint::Length(32),
|
|
||||||
]
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(chunks[0]);
|
|
||||||
|
|
||||||
let left_chunks = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints([Constraint::Length(5), Constraint::Min(4)].as_ref())
|
|
||||||
.split(main_chunks[0]);
|
|
||||||
|
|
||||||
let middle_chunks = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints(
|
|
||||||
[
|
|
||||||
Constraint::Min(4),
|
|
||||||
Constraint::Length(cmp::min(2 + self.message_compose.lines().len() as u16, 8)),
|
|
||||||
]
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.split(main_chunks[1]);
|
|
||||||
|
|
||||||
let right_chunks = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints([Constraint::Min(4)].as_ref())
|
|
||||||
.split(main_chunks[2]);
|
|
||||||
|
|
||||||
let mut status_content = Text::styled(
|
|
||||||
status.account_name(),
|
|
||||||
Style::default().add_modifier(Modifier::BOLD),
|
|
||||||
);
|
|
||||||
status_content.extend(Text::styled(status.account_user_id(), Style::default()));
|
|
||||||
status_content.extend(Text::styled(
|
|
||||||
"settings",
|
|
||||||
Style::default()
|
|
||||||
.fg(Color::LightMagenta)
|
|
||||||
.add_modifier(Modifier::ITALIC | Modifier::UNDERLINED),
|
|
||||||
));
|
|
||||||
|
|
||||||
let rooms_content = status
|
|
||||||
.rooms()
|
|
||||||
.iter()
|
|
||||||
.map(|(_, room)| ListItem::new(Span::styled(room.name(), Style::default())))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let messages_content = match status.room() {
|
|
||||||
Some(r) => {
|
|
||||||
r.timeline()
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.map(|event| {
|
|
||||||
match event {
|
|
||||||
// Message Like Events
|
|
||||||
AnyTimelineEvent::MessageLike(message_like_event) => {
|
|
||||||
let (content, color) = match &message_like_event {
|
|
||||||
AnyMessageLikeEvent::RoomMessage(room_message_event) => {
|
|
||||||
let message_content = &room_message_event
|
|
||||||
.as_original()
|
|
||||||
.unwrap()
|
|
||||||
.content
|
|
||||||
.body();
|
|
||||||
|
|
||||||
(message_content.to_string(), Color::White)
|
|
||||||
}
|
|
||||||
_ => (
|
|
||||||
format!(
|
|
||||||
"~~~ not supported message like event: {} ~~~",
|
|
||||||
message_like_event.event_type().to_string()
|
|
||||||
),
|
|
||||||
Color::Red,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
let mut text = Text::styled(
|
|
||||||
message_like_event.sender().to_string(),
|
|
||||||
Style::default()
|
|
||||||
.fg(Color::Cyan)
|
|
||||||
.add_modifier(Modifier::BOLD),
|
|
||||||
);
|
|
||||||
text.extend(Text::styled(
|
|
||||||
content.to_string(),
|
|
||||||
Style::default().fg(color),
|
|
||||||
));
|
|
||||||
ListItem::new(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// State Events
|
|
||||||
AnyTimelineEvent::State(state) => {
|
|
||||||
ListItem::new(vec![Spans::from(vec![
|
|
||||||
Span::styled(
|
|
||||||
state.sender().to_string(),
|
|
||||||
Style::default().fg(Color::DarkGray),
|
|
||||||
),
|
|
||||||
Span::styled(": ", Style::default().fg(Color::DarkGray)),
|
|
||||||
Span::styled(
|
|
||||||
state.event_type().to_string(),
|
|
||||||
Style::default().fg(Color::DarkGray),
|
|
||||||
),
|
|
||||||
])])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
vec![ListItem::new(Text::styled(
|
|
||||||
"No room selected!",
|
|
||||||
Style::default().fg(Color::Red),
|
|
||||||
))]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut messages_state = ListState::default();
|
|
||||||
let mut room_info_content = Text::default();
|
|
||||||
|
|
||||||
if let Some(room) = status.room() {
|
|
||||||
messages_state.select(room.view_scroll());
|
|
||||||
|
|
||||||
room_info_content.extend(Text::styled(room.name(), Style::default().fg(Color::Cyan)));
|
|
||||||
if room.encrypted() {
|
|
||||||
room_info_content
|
|
||||||
.extend(Text::styled("Encrypted", Style::default().fg(Color::Green)));
|
|
||||||
} else {
|
|
||||||
room_info_content.extend(Text::styled(
|
|
||||||
"Not Encrypted!",
|
|
||||||
Style::default().fg(Color::Red),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
room_info_content.extend(Text::styled(
|
|
||||||
"No room selected!",
|
|
||||||
Style::default().fg(Color::Red),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate to widgets colors, based of which widget is currently selected
|
|
||||||
let colors = match self.input_position {
|
|
||||||
MainInputPosition::Status => {
|
|
||||||
textarea_inactivate(&mut self.message_compose);
|
|
||||||
if let Some(cli) = &mut self.cli {
|
|
||||||
textarea_inactivate(cli);
|
|
||||||
}
|
|
||||||
vec![
|
|
||||||
Color::White,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
MainInputPosition::Rooms => {
|
|
||||||
textarea_inactivate(&mut self.message_compose);
|
|
||||||
if let Some(cli) = &mut self.cli {
|
|
||||||
textarea_inactivate(cli);
|
|
||||||
}
|
|
||||||
vec![
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::White,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
MainInputPosition::Messages => {
|
|
||||||
textarea_inactivate(&mut self.message_compose);
|
|
||||||
if let Some(cli) = &mut self.cli {
|
|
||||||
textarea_inactivate(cli);
|
|
||||||
}
|
|
||||||
vec![
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::White,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
MainInputPosition::MessageCompose => {
|
|
||||||
textarea_activate(&mut self.message_compose);
|
|
||||||
if let Some(cli) = &mut self.cli {
|
|
||||||
textarea_inactivate(cli);
|
|
||||||
}
|
|
||||||
vec![
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
MainInputPosition::RoomInfo => {
|
|
||||||
textarea_inactivate(&mut self.message_compose);
|
|
||||||
if let Some(cli) = &mut self.cli {
|
|
||||||
textarea_inactivate(cli);
|
|
||||||
}
|
|
||||||
vec![
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::White,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
MainInputPosition::CLI => {
|
|
||||||
textarea_inactivate(&mut self.message_compose);
|
|
||||||
if let Some(cli) = &mut self.cli {
|
|
||||||
textarea_activate(cli);
|
|
||||||
}
|
|
||||||
vec![
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
Color::DarkGray,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// initiate the widgets
|
|
||||||
let status_panel = Paragraph::new(status_content)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.title("Status")
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.style(Style::default().fg(colors[MainInputPosition::Status as usize])),
|
|
||||||
)
|
|
||||||
.alignment(Alignment::Left);
|
|
||||||
|
|
||||||
let rooms_panel = List::new(rooms_content)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.title("Rooms (navigate: arrow keys)")
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.style(Style::default().fg(colors[MainInputPosition::Rooms as usize])),
|
|
||||||
)
|
|
||||||
.style(Style::default().fg(Color::DarkGray))
|
|
||||||
.highlight_style(
|
|
||||||
Style::default()
|
|
||||||
.fg(Color::Cyan)
|
|
||||||
.add_modifier(Modifier::BOLD),
|
|
||||||
)
|
|
||||||
.highlight_symbol(">");
|
|
||||||
|
|
||||||
let messages_panel = List::new(messages_content)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.title("Messages")
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.style(Style::default().fg(colors[MainInputPosition::Messages as usize])),
|
|
||||||
)
|
|
||||||
.start_corner(Corner::BottomLeft)
|
|
||||||
.highlight_symbol(">")
|
|
||||||
.highlight_style(
|
|
||||||
Style::default()
|
|
||||||
.fg(Color::LightMagenta)
|
|
||||||
.add_modifier(Modifier::BOLD),
|
|
||||||
);
|
|
||||||
|
|
||||||
let room_info_panel = Paragraph::new(room_info_content)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.title("Room Info")
|
|
||||||
.borders(Borders::ALL)
|
|
||||||
.style(Style::default().fg(colors[MainInputPosition::RoomInfo as usize])),
|
|
||||||
)
|
|
||||||
.alignment(Alignment::Center);
|
|
||||||
|
|
||||||
// render the widgets
|
|
||||||
self.terminal.draw(|frame| {
|
|
||||||
frame.render_widget(status_panel, left_chunks[0]);
|
|
||||||
frame.render_stateful_widget(rooms_panel, left_chunks[1], &mut self.rooms_state);
|
|
||||||
frame.render_stateful_widget(messages_panel, middle_chunks[0], &mut messages_state);
|
|
||||||
frame.render_widget(self.message_compose.widget(), middle_chunks[1]);
|
|
||||||
match &self.cli {
|
|
||||||
Some(cli) => frame.render_widget(cli.widget(), chunks[1]),
|
|
||||||
None => (),
|
|
||||||
};
|
|
||||||
frame.render_widget(room_info_panel, right_chunks[0]);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_setup(&mut self) -> Result<()> {
|
|
||||||
let ui = match &mut self.setup_ui {
|
|
||||||
Some(c) => c,
|
|
||||||
None => return Err(Error::msg("SetupUI instance not found")),
|
|
||||||
};
|
|
||||||
|
|
||||||
ui.update(&mut self.terminal).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
use std::io::Stdout;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use tui::{
|
||||||
|
backend::CrosstermBackend,
|
||||||
|
layout::{Constraint, Direction, Layout, Alignment},
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
text::Span,
|
||||||
|
widgets::{Block, Borders, Paragraph},
|
||||||
|
Terminal,
|
||||||
|
};
|
||||||
|
use tui_textarea::TextArea;
|
||||||
|
|
||||||
|
use crate::ui::{textarea_activate, textarea_inactivate};
|
||||||
|
|
||||||
|
pub struct UI<'a> {
|
||||||
|
input_position: InputPosition,
|
||||||
|
|
||||||
|
pub homeserver: TextArea<'a>,
|
||||||
|
pub username: TextArea<'a>,
|
||||||
|
pub password: TextArea<'a>,
|
||||||
|
pub password_data: TextArea<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum InputPosition {
|
||||||
|
Homeserver,
|
||||||
|
Username,
|
||||||
|
Password,
|
||||||
|
Ok,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UI<'_> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut homeserver = TextArea::new(vec!["https://matrix.org".to_string()]);
|
||||||
|
let mut username = TextArea::default();
|
||||||
|
let mut password = TextArea::default();
|
||||||
|
let password_data = TextArea::default();
|
||||||
|
|
||||||
|
homeserver.set_block(Block::default().title("Homeserver").borders(Borders::ALL));
|
||||||
|
username.set_block(Block::default().title("Username").borders(Borders::ALL));
|
||||||
|
password.set_block(Block::default().title("Password").borders(Borders::ALL));
|
||||||
|
|
||||||
|
textarea_activate(&mut homeserver);
|
||||||
|
textarea_inactivate(&mut username);
|
||||||
|
textarea_inactivate(&mut password);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
input_position: InputPosition::Homeserver,
|
||||||
|
homeserver,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
password_data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cycle_input_position(&mut self) {
|
||||||
|
self.input_position = match self.input_position {
|
||||||
|
InputPosition::Homeserver => {
|
||||||
|
textarea_inactivate(&mut self.homeserver);
|
||||||
|
textarea_activate(&mut self.username);
|
||||||
|
textarea_inactivate(&mut self.password);
|
||||||
|
InputPosition::Username
|
||||||
|
}
|
||||||
|
InputPosition::Username => {
|
||||||
|
textarea_inactivate(&mut self.homeserver);
|
||||||
|
textarea_inactivate(&mut self.username);
|
||||||
|
textarea_activate(&mut self.password);
|
||||||
|
InputPosition::Password
|
||||||
|
}
|
||||||
|
InputPosition::Password => {
|
||||||
|
textarea_inactivate(&mut self.homeserver);
|
||||||
|
textarea_inactivate(&mut self.username);
|
||||||
|
textarea_inactivate(&mut self.password);
|
||||||
|
InputPosition::Ok
|
||||||
|
}
|
||||||
|
InputPosition::Ok => {
|
||||||
|
textarea_activate(&mut self.homeserver);
|
||||||
|
textarea_inactivate(&mut self.username);
|
||||||
|
textarea_inactivate(&mut self.password);
|
||||||
|
InputPosition::Homeserver
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cycle_input_position_rev(&mut self) {
|
||||||
|
self.input_position = match self.input_position {
|
||||||
|
InputPosition::Homeserver => {
|
||||||
|
textarea_inactivate(&mut self.homeserver);
|
||||||
|
textarea_inactivate(&mut self.username);
|
||||||
|
textarea_inactivate(&mut self.password);
|
||||||
|
InputPosition::Ok
|
||||||
|
}
|
||||||
|
InputPosition::Username => {
|
||||||
|
textarea_activate(&mut self.homeserver);
|
||||||
|
textarea_inactivate(&mut self.username);
|
||||||
|
textarea_inactivate(&mut self.password);
|
||||||
|
InputPosition::Homeserver
|
||||||
|
}
|
||||||
|
InputPosition::Password => {
|
||||||
|
textarea_inactivate(&mut self.homeserver);
|
||||||
|
textarea_activate(&mut self.username);
|
||||||
|
textarea_inactivate(&mut self.password);
|
||||||
|
InputPosition::Username
|
||||||
|
}
|
||||||
|
InputPosition::Ok => {
|
||||||
|
textarea_inactivate(&mut self.homeserver);
|
||||||
|
textarea_inactivate(&mut self.username);
|
||||||
|
textarea_activate(&mut self.password);
|
||||||
|
InputPosition::Password
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn input_position(&self) -> &InputPosition {
|
||||||
|
&self.input_position
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update(
|
||||||
|
&mut self,
|
||||||
|
terminal: &mut Terminal<CrosstermBackend<Stdout>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let strings: Vec<String> = vec!["".to_owned(); 3];
|
||||||
|
|
||||||
|
let content_ok = match self.input_position {
|
||||||
|
InputPosition::Ok => {
|
||||||
|
Span::styled("OK", Style::default().add_modifier(Modifier::UNDERLINED))
|
||||||
|
}
|
||||||
|
_ => Span::styled("OK", Style::default().fg(Color::DarkGray)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let block = Block::default().title("Login").borders(Borders::ALL);
|
||||||
|
|
||||||
|
let ok = Paragraph::new(content_ok).alignment(Alignment::Center);
|
||||||
|
|
||||||
|
// define a 32 * 6 chunk in the middle of the screen
|
||||||
|
let mut chunk = terminal.size()?;
|
||||||
|
chunk.x = (chunk.width / 2) - 16;
|
||||||
|
chunk.y = (chunk.height / 2) - 5;
|
||||||
|
chunk.height = 12;
|
||||||
|
chunk.width = 32;
|
||||||
|
|
||||||
|
let mut split_chunk = chunk.clone();
|
||||||
|
split_chunk.x += 1;
|
||||||
|
split_chunk.y += 1;
|
||||||
|
split_chunk.height -= 1;
|
||||||
|
split_chunk.width -= 2;
|
||||||
|
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(
|
||||||
|
[
|
||||||
|
Constraint::Length(3), // 0. Homserver:
|
||||||
|
Constraint::Length(3), // 1. Username:
|
||||||
|
Constraint::Length(3), // 2. Password:
|
||||||
|
Constraint::Length(1), // 3. OK
|
||||||
|
]
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.split(split_chunk);
|
||||||
|
|
||||||
|
terminal.draw(|frame| {
|
||||||
|
frame.render_widget(block.clone(), chunk);
|
||||||
|
frame.render_widget(self.homeserver.widget(), chunks[0]);
|
||||||
|
frame.render_widget(self.username.widget(), chunks[1]);
|
||||||
|
frame.render_widget(self.password.widget(), chunks[2]);
|
||||||
|
frame.render_widget(ok.clone(), chunks[3]);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue