Preparing for v2.0 (#74)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Xithrius 2022-02-16 16:09:39 -08:00 committed by GitHub
parent 5893ae8c60
commit 9ac53ca891
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 701 additions and 242 deletions

3
.gitignore vendored
View File

@ -14,9 +14,6 @@
# JetBrains project config folder. # JetBrains project config folder.
.idea .idea
# Visual Studio Code config folder.
.vscode
# Ignore Vim temporary and swap files. # Ignore Vim temporary and swap files.
*.sw? *.sw?
*~ *~

42
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,42 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug executable 'twt'",
"cargo": {
"args": [
"build",
"--bin=twt",
"--package=twitch-tui"
],
"filter": {
"name": "twt",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in executable 'twt'",
"cargo": {
"args": [
"test",
"--no-run",
"--bin=twt",
"--package=twitch-tui"
],
"filter": {
"name": "twt",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"editor.defaultFormatter": "matklad.rust-analyzer"
}

332
Cargo.lock generated
View File

@ -39,9 +39,9 @@ dependencies = [
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
@ -63,9 +63,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.72" version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -103,9 +103,9 @@ dependencies = [
[[package]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "4.2.2" version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db8340083d28acb43451166543b98c838299b7e0863621be53a338adceea0ed" checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db"
dependencies = [ dependencies = [
"error-code", "error-code",
"str-buf", "str-buf",
@ -114,9 +114,9 @@ dependencies = [
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.2" version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [ dependencies = [
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -290,20 +290,50 @@ dependencies = [
] ]
[[package]] [[package]]
name = "error-code" name = "errno"
version = "2.3.0" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5115567ac25674e0043e472be13d14e537f37ea8aa4bdc4aef0c89add1db1ff" checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "error-code"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
dependencies = [ dependencies = [
"libc", "libc",
"str-buf", "str-buf",
] ]
[[package]] [[package]]
name = "fd-lock" name = "fastrand"
version = "3.0.1" version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfc110fe50727d46a428eed832df40affe9bf74d077cac1bf3f2718e823f14c5" checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
dependencies = [
"instant",
]
[[package]]
name = "fd-lock"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcef756dea9cf3db5ce73759cf0467330427a786b47711b8d6c97620d718ceb9"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@ -415,10 +445,19 @@ dependencies = [
] ]
[[package]] [[package]]
name = "getrandom" name = "fuzzy-matcher"
version = "0.2.3" version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
dependencies = [
"thread_local",
]
[[package]]
name = "getrandom"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@ -452,6 +491,15 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "io-lifetimes"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ef6787e7f0faedc040f95716bdd0e62bcfcf4ba93da053b62dea2691c13864"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "irc" name = "irc"
version = "0.15.0" version = "0.15.0"
@ -527,9 +575,9 @@ checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
version = "0.6.4" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
@ -598,9 +646,9 @@ dependencies = [
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.23.0" version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188" checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cc", "cc",
@ -611,9 +659,9 @@ dependencies = [
[[package]] [[package]]
name = "ntapi" name = "ntapi"
version = "0.3.6" version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
dependencies = [ dependencies = [
"winapi", "winapi",
] ]
@ -639,9 +687,9 @@ dependencies = [
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.13.0" version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi",
"libc", "libc",
@ -649,9 +697,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.8.0" version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]] [[package]]
name = "openssl" name = "openssl"
@ -669,15 +717,15 @@ dependencies = [
[[package]] [[package]]
name = "openssl-probe" name = "openssl-probe"
version = "0.1.4" version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.71" version = "0.9.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df13d165e607909b363a4757a6f133f8a818a74e9d3a98d09c6128e15fa4c73" checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"cc", "cc",
@ -736,18 +784,18 @@ dependencies = [
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.0.8" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
dependencies = [ dependencies = [
"pin-project-internal", "pin-project-internal",
] ]
[[package]] [[package]]
name = "pin-project-internal" name = "pin-project-internal"
version = "1.0.8" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -756,9 +804,9 @@ dependencies = [
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.7" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@ -768,15 +816,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.22" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
@ -804,18 +846,18 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.32" version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.10" 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 = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -830,46 +872,6 @@ dependencies = [
"nibble_vec", "nibble_vec",
] ]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.10" version = "0.2.10"
@ -915,6 +917,20 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "rustix"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cee647393af53c750e15dcbf7781cdd2e550b246bde76e46c326e7ea3c73773"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"winapi",
]
[[package]] [[package]]
name = "rustyline" name = "rustyline"
version = "9.1.2" version = "9.1.2"
@ -957,9 +973,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.3.1" version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"core-foundation", "core-foundation",
@ -970,9 +986,9 @@ dependencies = [
[[package]] [[package]]
name = "security-framework-sys" name = "security-framework-sys"
version = "2.4.2" version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
dependencies = [ dependencies = [
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -1000,9 +1016,9 @@ dependencies = [
[[package]] [[package]]
name = "signal-hook" name = "signal-hook"
version = "0.3.10" version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1" checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d"
dependencies = [ dependencies = [
"libc", "libc",
"signal-hook-registry", "signal-hook-registry",
@ -1036,9 +1052,9 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]] [[package]]
name = "smawk" name = "smawk"
@ -1094,9 +1110,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.82" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1105,13 +1121,13 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.2.0" version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand",
"libc", "libc",
"rand",
"redox_syscall", "redox_syscall",
"remove_dir_all", "remove_dir_all",
"winapi", "winapi",
@ -1157,6 +1173,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "thread_local"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
dependencies = [
"once_cell",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.43" version = "0.1.43"
@ -1264,6 +1289,7 @@ dependencies = [
"crossterm 0.20.0", "crossterm 0.20.0",
"enum-iterator", "enum-iterator",
"futures", "futures",
"fuzzy-matcher",
"irc", "irc",
"lazy_static", "lazy_static",
"rustyline", "rustyline",
@ -1358,9 +1384,35 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.28.0" version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82ca39602d5cbfa692c4b67e3bcbb2751477355141c1ed434c94da4186836ff6" checksum = "030b7ff91626e57a05ca64a07c481973cbb2db774e4852c9c7ca342408c6a99a"
dependencies = [
"windows_aarch64_msvc 0.28.0",
"windows_i686_gnu 0.28.0",
"windows_i686_msvc 0.28.0",
"windows_x86_64_gnu 0.28.0",
"windows_x86_64_msvc 0.28.0",
]
[[package]]
name = "windows-sys"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6"
dependencies = [
"windows_aarch64_msvc 0.32.0",
"windows_i686_gnu 0.32.0",
"windows_i686_msvc 0.32.0",
"windows_x86_64_gnu 0.32.0",
"windows_x86_64_msvc 0.32.0",
]
[[package]]
name = "windows-sys"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6"
dependencies = [ dependencies = [
"windows_aarch64_msvc 0.28.0", "windows_aarch64_msvc 0.28.0",
"windows_i686_gnu 0.28.0", "windows_i686_gnu 0.28.0",
@ -1384,9 +1436,9 @@ dependencies = [
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.28.0" version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52695a41e536859d5308cc613b4a022261a274390b25bd29dfff4bf08505f3c2" checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
@ -1396,9 +1448,27 @@ checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.28.0" version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54725ac23affef038fecb177de6c9bf065787c2f432f79e3c373da92f3e1d8a" checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
[[package]]
name = "windows_aarch64_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5"
[[package]]
name = "windows_i686_gnu"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
@ -1408,9 +1478,15 @@ checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.28.0" version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d5158a43cc43623c0729d1ad6647e62fa384a3d135fd15108d37c683461f64" checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6"
[[package]]
name = "windows_i686_gnu"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
@ -1420,9 +1496,15 @@ checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.28.0" version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc31f409f565611535130cfe7ee8e6655d3fa99c1c61013981e491921b5ce954" checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a"
[[package]]
name = "windows_i686_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
@ -1432,7 +1514,31 @@ checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.28.0" version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1"
[[package]]
name = "windows_i686_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172"
[[package]]
name = "windows_x86_64_gnu"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
[[package]]
name = "windows_x86_64_gnu"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f" checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f"

View File

@ -28,6 +28,7 @@ rustyline = "9.1.2"
lazy_static = "1.4.0" lazy_static = "1.4.0"
structopt = "0.3.26" structopt = "0.3.26"
enum-iterator = "0.7.0" enum-iterator = "0.7.0"
fuzzy-matcher = "0.3.7"
[[bin]] [[bin]]
bench = false bench = false

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2021 Xithrius Copyright (c) 2022 Xithrius
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -11,12 +11,14 @@ pub enum State {
Input, Input,
Help, Help,
ChannelSwitch, ChannelSwitch,
Search,
} }
#[derive(PartialEq, std::cmp::Eq, std::hash::Hash, IntoEnumIterator)] #[derive(PartialEq, std::cmp::Eq, std::hash::Hash, IntoEnumIterator)]
pub enum BufferName { pub enum BufferName {
Chat, Chat,
Channel, Channel,
MessageSearch,
} }
pub struct App { pub struct App {
@ -32,6 +34,10 @@ pub struct App {
pub table_constraints: Option<Vec<Constraint>>, pub table_constraints: Option<Vec<Constraint>>,
/// The titles of the columns within the table of the terminal /// The titles of the columns within the table of the terminal
pub column_titles: Option<Vec<String>>, pub column_titles: Option<Vec<String>>,
/// Scrolling offset for windows
pub scroll_offset: usize,
/// A temporary snapshot of current messages
pub messages_snapshot: VecDeque<Data>,
} }
impl App { impl App {
@ -49,10 +55,16 @@ impl App {
input_buffers: input_buffers_map, input_buffers: input_buffers_map,
table_constraints: None, table_constraints: None,
column_titles: None, column_titles: None,
scroll_offset: 0,
messages_snapshot: VecDeque::with_capacity(config.terminal.maximum_messages),
} }
} }
pub fn get_buffer(&mut self) -> &mut LineBuffer { pub fn current_buffer(&self) -> &LineBuffer {
return self.input_buffers.get(&self.selected_buffer).unwrap();
}
pub fn current_buffer_mut(&mut self) -> &mut LineBuffer {
return self.input_buffers.get_mut(&self.selected_buffer).unwrap(); return self.input_buffers.get_mut(&self.selected_buffer).unwrap();
} }
} }

View File

@ -9,6 +9,13 @@ use crate::{
utils::{colors::hsl_to_rgb, styles, text::align_text}, utils::{colors::hsl_to_rgb, styles, text::align_text},
}; };
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum PayLoad {
Message(String),
Err(String),
}
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct DataBuilder<'conf> { pub struct DataBuilder<'conf> {
pub date_format: &'conf str, pub date_format: &'conf str,
@ -24,7 +31,7 @@ impl<'conf> DataBuilder<'conf> {
time_sent: Local::now().format(self.date_format).to_string(), time_sent: Local::now().format(self.date_format).to_string(),
author: user, author: user,
system: false, system: false,
message, payload: PayLoad::Message(message),
} }
} }
@ -33,7 +40,7 @@ impl<'conf> DataBuilder<'conf> {
time_sent: Local::now().format(self.date_format).to_string(), time_sent: Local::now().format(self.date_format).to_string(),
author: "System".to_string(), author: "System".to_string(),
system: true, system: true,
message, payload: PayLoad::Message(message),
} }
} }
@ -42,7 +49,7 @@ impl<'conf> DataBuilder<'conf> {
time_sent: Local::now().format(self.date_format).to_string(), time_sent: Local::now().format(self.date_format).to_string(),
author: "Twitch".to_string(), author: "Twitch".to_string(),
system: true, system: true,
message, payload: PayLoad::Message(message),
} }
} }
} }
@ -51,8 +58,8 @@ impl<'conf> DataBuilder<'conf> {
pub struct Data { pub struct Data {
pub time_sent: String, pub time_sent: String,
pub author: String, pub author: String,
pub message: String,
pub system: bool, pub system: bool,
pub payload: PayLoad,
} }
impl Data { impl Data {
@ -77,38 +84,41 @@ impl Data {
} }
pub fn to_row(&self, frontend_config: &FrontendConfig, limit: &usize) -> (u16, Row) { pub fn to_row(&self, frontend_config: &FrontendConfig, limit: &usize) -> (u16, Row) {
let message = textwrap::fill(self.message.as_str(), *limit); if let PayLoad::Message(m) = &self.payload {
let message = textwrap::fill(m.as_str(), *limit);
let style; let style = if self.system {
if self.system { styles::SYSTEM_CHAT
style = styles::SYSTEM_CHAT; } else {
Style::default().fg(self.hash_username(&frontend_config.palette))
};
let mut row_vector = vec![
Cell::from(align_text(
&self.author,
frontend_config.username_alignment.as_str(),
frontend_config.maximum_username_length,
))
.style(style),
Cell::from(message.to_string()),
];
if frontend_config.date_shown {
row_vector.insert(0, Cell::from(self.time_sent.to_string()));
}
let msg_height = message.split('\n').count() as u16;
let mut row = Row::new(row_vector).style(styles::CHAT);
if msg_height > 1 {
row = row.height(msg_height);
}
(msg_height, row)
} else { } else {
style = Style::default().fg(self.hash_username(&frontend_config.palette)); panic!("Data.to_row() can only take message payloads.")
} }
let mut row_vector = vec![
Cell::from(align_text(
&self.author,
frontend_config.username_alignment.as_str(),
frontend_config.maximum_username_length,
))
.style(style),
Cell::from(message.to_string()),
];
if frontend_config.date_shown {
row_vector.insert(0, Cell::from(self.time_sent.to_string()));
}
let msg_height = message.split('\n').count() as u16;
let mut row = Row::new(row_vector).style(styles::CHAT);
if msg_height > 1 {
row = row.height(msg_height);
}
(msg_height, row)
} }
} }
@ -123,8 +133,8 @@ mod tests {
Data { Data {
time_sent: Local::now().format("%c").to_string(), time_sent: Local::now().format("%c").to_string(),
author: "human".to_string(), author: "human".to_string(),
message: "beep boop".to_string(),
system: false, system: false,
payload: PayLoad::Message("beep boop".to_string()),
} }
} }

View File

@ -1,3 +1,9 @@
mod handlers;
mod terminal;
mod twitch;
mod ui;
mod utils;
use anyhow::Result; use anyhow::Result;
use structopt::StructOpt; use structopt::StructOpt;
use tokio::sync::mpsc; use tokio::sync::mpsc;
@ -9,12 +15,6 @@ use handlers::{
use crate::handlers::app::App; use crate::handlers::app::App;
mod handlers;
mod terminal;
mod twitch;
mod ui;
mod utils;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
match CompleteConfig::new() { match CompleteConfig::new() {

View File

@ -18,7 +18,7 @@ use crate::{
handlers::{ handlers::{
app::{App, BufferName, State}, app::{App, BufferName, State},
config::CompleteConfig, config::CompleteConfig,
data::{Data, DataBuilder}, data::{Data, DataBuilder, PayLoad},
event::{Config, Event, Events, Key}, event::{Config, Event, Events, Key},
}, },
twitch::Action, twitch::Action,
@ -96,24 +96,47 @@ pub async fn ui_driver(
}; };
'outer: loop { 'outer: loop {
if let Ok(info) = rx.try_recv() {
match info.payload {
PayLoad::Message(_) => app.messages.push_front(info),
// If something such as a keypress failed, fallback to the normal state of the application.
PayLoad::Err(err) => {
app.state = State::Normal;
app.selected_buffer = BufferName::Chat;
app.messages.push_front(data_builder.system(err));
}
}
}
terminal terminal
.draw(|frame| draw_ui(frame, &mut app, &config)) .draw(|frame| draw_ui(frame, &mut app, &config))
.unwrap(); .unwrap();
if let Ok(info) = rx.try_recv() {
app.messages.push_front(info);
}
if let Some(Event::Input(key)) = events.next().await { if let Some(Event::Input(key)) = events.next().await {
match app.state { match app.state {
State::Input | State::ChannelSwitch => { State::Input | State::ChannelSwitch | State::Search => {
let input_buffer = app.get_buffer(); let input_buffer = app.current_buffer_mut();
match key { match key {
Key::Up => { Key::Up => match app.state {
if let State::Input = app.state { State::Input => {
app.state = State::Normal; app.state = State::Normal;
} }
State::Search => {
if app.scroll_offset > 1 {
app.scroll_offset -= 1;
}
}
_ => {}
},
Key::Down => {
if let State::Search = app.state {
if app.scroll_offset < app.messages_snapshot.len() {
app.scroll_offset += 1;
}
}
} }
Key::Ctrl('f') | Key::Right => { Key::Ctrl('f') | Key::Right => {
input_buffer.move_forward(1); input_buffer.move_forward(1);
@ -169,6 +192,7 @@ pub async fn ui_driver(
.await .await
.unwrap(); .unwrap();
} }
input_message.update("", 0); input_message.update("", 0);
} }
BufferName::Channel => { BufferName::Channel => {
@ -184,11 +208,13 @@ pub async fn ui_driver(
config.twitch.channel = input_message.to_string(); config.twitch.channel = input_message.to_string();
} }
input_message.update("", 0); input_message.update("", 0);
app.selected_buffer = BufferName::Chat; app.selected_buffer = BufferName::Chat;
app.state = State::Normal; app.state = State::Normal;
} }
BufferName::MessageSearch => {}
}, },
Key::Char(c) => { Key::Char(c) => {
input_buffer.insert(c, 1); input_buffer.insert(c, 1);
@ -205,12 +231,20 @@ pub async fn ui_driver(
app.state = State::Normal; app.state = State::Normal;
app.selected_buffer = BufferName::Chat; app.selected_buffer = BufferName::Chat;
} }
Key::Char('?') => app.state = State::Help,
Key::Char('i') => app.state = State::Input,
Key::Char('C') => { Key::Char('C') => {
app.state = State::ChannelSwitch; app.state = State::ChannelSwitch;
app.selected_buffer = BufferName::Channel; app.selected_buffer = BufferName::Channel;
} }
Key::Char('?') => app.state = State::Help,
Key::Char('i') => {
app.state = State::Input;
app.selected_buffer = BufferName::Chat;
}
Key::Char('s') => {
app.state = State::Search;
app.selected_buffer = BufferName::MessageSearch;
app.messages_snapshot = app.messages.clone();
}
Key::Char('q') => { Key::Char('q') => {
quitting(terminal); quitting(terminal);
break 'outer; break 'outer;
@ -220,11 +254,10 @@ pub async fn ui_driver(
quitting(terminal); quitting(terminal);
break 'outer; break 'outer;
} }
State::ChannelSwitch => { _ => {
app.selected_buffer = BufferName::Chat;
app.state = State::Normal; app.state = State::Normal;
app.selected_buffer = BufferName::Chat;
} }
_ => app.state = State::Normal,
}, },
_ => {} _ => {}
}, },

View File

@ -29,7 +29,9 @@ pub async fn twitch_irc(mut config: CompleteConfig, tx: Sender<Data>, mut rx: Re
}; };
let mut client = Client::from_config(irc_config.clone()).await.unwrap(); let mut client = Client::from_config(irc_config.clone()).await.unwrap();
client.identify().unwrap(); client.identify().unwrap();
let mut stream = client.stream().unwrap(); let mut stream = client.stream().unwrap();
let data_builder = DataBuilder::new(&config.frontend.date_format); let data_builder = DataBuilder::new(&config.frontend.date_format);
let mut room_state_startup = false; let mut room_state_startup = false;
@ -57,17 +59,19 @@ pub async fn twitch_irc(mut config: CompleteConfig, tx: Sender<Data>, mut rx: Re
biased; biased;
Some(action) = rx.recv() => { Some(action) = rx.recv() => {
let current_channel = format!("#{}", config.twitch.channel);
match action { match action {
Action::Privmsg(message) => { Action::Privmsg(message) => {
client client
.send_privmsg(format!("#{}", config.twitch.channel), message) .send_privmsg(current_channel, message)
.unwrap(); .unwrap();
} }
Action::Join(channel) => { Action::Join(channel) => {
let channel_list = format!("#{}", channel); let channel_list = format!("#{}", channel);
// Leave previous channel // Leave previous channel
if let Err(err) = client.send_part(format!("#{}", config.twitch.channel)) { if let Err(err) = client.send_part(current_channel) {
tx.send(data_builder.twitch(err.to_string())).await.unwrap() tx.send(data_builder.twitch(err.to_string())).await.unwrap()
} else { } else {
tx.send(data_builder.twitch(format!("Joined {}", channel_list))).await.unwrap(); tx.send(data_builder.twitch(format!("Joined {}", channel_list))).await.unwrap();
@ -85,7 +89,7 @@ pub async fn twitch_irc(mut config: CompleteConfig, tx: Sender<Data>, mut rx: Re
} }
Some(_message) = stream.next() => { Some(_message) = stream.next() => {
if let Ok(message) = _message { if let Ok(message) = _message {
let mut tags: HashMap<&str, &str> = std::collections::HashMap::new(); let mut tags: HashMap<&str, &str> = HashMap::new();
if let Some(ref _tags) = message.tags { if let Some(ref _tags) = message.tags {
for tag in _tags { for tag in _tags {
if let Some(ref tag_value) = tag.1 { if let Some(ref tag_value) = tag.1 {

View File

@ -8,7 +8,7 @@ use tui::{
style::{Color, Modifier, Style}, style::{Color, Modifier, Style},
terminal::Frame, terminal::Frame,
text::{Span, Spans}, text::{Span, Spans},
widgets::{Block, Borders, Clear, Paragraph, Row, Table}, widgets::{Block, Borders, Paragraph, Row, Table},
}; };
use crate::{ use crate::{
@ -16,10 +16,7 @@ use crate::{
app::{App, State}, app::{App, State},
config::CompleteConfig, config::CompleteConfig,
}, },
ui::{ ui::statics::COMMANDS,
popups::{centered_popup, Centering},
statics::COMMANDS,
},
utils::{styles, text::get_cursor_position}, utils::{styles, text::get_cursor_position},
}; };
@ -122,7 +119,7 @@ pub fn draw_ui<T: Backend>(frame: &mut Frame<T>, app: &mut App, config: &Complet
match app.state { match app.state {
State::Input => { State::Input => {
let input_buffer = app.get_buffer(); let input_buffer = app.current_buffer();
if input_buffer.starts_with('/') { if input_buffer.starts_with('/') {
let suggested_commands = COMMANDS let suggested_commands = COMMANDS
@ -165,31 +162,9 @@ pub fn draw_ui<T: Backend>(frame: &mut Frame<T>, app: &mut App, config: &Complet
vertical_chunks[vertical_chunk_constraints.len() - 1], vertical_chunks[vertical_chunk_constraints.len() - 1],
); );
} }
State::Help => popups::help::keybinds(frame), State::Help => popups::help::show_keybinds(frame),
State::ChannelSwitch => { State::ChannelSwitch => popups::channels::switch_channels(frame, app),
let input_rect = centered_popup(Centering::Input(30, 10), frame.size()); State::Search => popups::messages::search_messages(frame, app),
_ => {}
let input_buffer = app.get_buffer();
let cursor_pos = get_cursor_position(input_buffer);
frame.set_cursor(
(input_rect.x + cursor_pos as u16 + 1)
.min(input_rect.x + input_rect.width.saturating_sub(2)),
input_rect.y + 1,
);
let paragraph = Paragraph::new(input_buffer.as_str())
.style(Style::default().fg(Color::Yellow))
.block(Block::default().borders(Borders::ALL).title("[ Channel ]"))
.scroll((
0,
((cursor_pos + 3) as u16).saturating_sub(input_rect.width),
));
frame.render_widget(Clear, input_rect);
frame.render_widget(paragraph, input_rect);
}
_ => (),
} }
} }

37
src/ui/popups/channels.rs Normal file
View File

@ -0,0 +1,37 @@
use tui::{
backend::Backend,
style::{Color, Style},
terminal::Frame,
widgets::{Block, Borders, Clear, Paragraph},
};
use crate::{
handlers::app::App,
ui::popups::{centered_popup, WindowType},
utils::text::get_cursor_position,
};
pub fn switch_channels<T: Backend>(frame: &mut Frame<T>, app: &mut App) {
let input_rect = centered_popup(WindowType::Input(frame.size().height), frame.size());
let input_buffer = app.current_buffer();
let cursor_pos = get_cursor_position(input_buffer);
frame.set_cursor(
(input_rect.x + cursor_pos as u16 + 1)
.min(input_rect.x + input_rect.width.saturating_sub(2)),
input_rect.y + 1,
);
let paragraph = Paragraph::new(input_buffer.as_str())
.style(Style::default().fg(Color::Yellow))
.block(Block::default().borders(Borders::ALL).title("[ Channel ]"))
.scroll((
0,
((cursor_pos + 3) as u16).saturating_sub(input_rect.width),
));
frame.render_widget(Clear, input_rect);
frame.render_widget(paragraph, input_rect);
}

View File

@ -7,13 +7,13 @@ use tui::{
use crate::{ use crate::{
ui::{ ui::{
popups::{centered_popup, Centering}, popups::{centered_popup, Centering, WindowType},
statics::{HELP_COLUMN_TITLES, HELP_KEYBINDS}, statics::{HELP_COLUMN_TITLES, HELP_KEYBINDS},
}, },
utils::{styles, text::vector_column_max}, utils::{styles, text::vector_column_max},
}; };
pub fn keybinds<T: Backend>(frame: &mut Frame<T>) { pub fn show_keybinds<T: Backend>(frame: &mut Frame<T>) {
let table_widths = vector_column_max(&HELP_KEYBINDS, None) let table_widths = vector_column_max(&HELP_KEYBINDS, None)
.into_iter() .into_iter()
.map(Constraint::Min) .map(Constraint::Min)
@ -26,7 +26,13 @@ pub fn keybinds<T: Backend>(frame: &mut Frame<T>) {
.column_spacing(2) .column_spacing(2)
.style(styles::BORDER_NAME); .style(styles::BORDER_NAME);
let area = centered_popup(Centering::Window(60, 50), frame.size()); let area = centered_popup(
WindowType::Window(
Centering::Middle(frame.size().height),
HELP_KEYBINDS.len() as u16,
),
frame.size(),
);
frame.render_widget(Clear, area); frame.render_widget(Clear, area);
frame.render_widget(help_table, area); frame.render_widget(help_table, area);

142
src/ui/popups/messages.rs Normal file
View File

@ -0,0 +1,142 @@
use std::collections::VecDeque;
use tui::{
backend::Backend,
layout::Constraint,
style::{Color, Modifier, Style},
terminal::Frame,
text::{Span, Spans},
widgets::{Block, Borders, Clear, Paragraph, Row, Table},
};
use fuzzy_matcher::skim::SkimMatcherV2;
use lazy_static::lazy_static;
use crate::{
handlers::{app::App, data::PayLoad},
ui::popups::{centered_popup, scroll_view, Centering, WindowType},
utils::{styles, text::get_cursor_position},
};
use fuzzy_matcher::FuzzyMatcher;
const MAX_MESSAGE_SEARCH: u16 = 10;
lazy_static! {
pub static ref FUZZY_FINDER: SkimMatcherV2 = SkimMatcherV2::default();
}
pub fn search_messages<T: Backend>(frame: &mut Frame<T>, app: &mut App) {
let input_rect = centered_popup(WindowType::Input(frame.size().height), frame.size());
let window_rect = centered_popup(
WindowType::Window(Centering::Height(frame.size().height), MAX_MESSAGE_SEARCH),
frame.size(),
);
let input_buffer = app.current_buffer();
let cursor_pos = get_cursor_position(input_buffer);
frame.set_cursor(
(input_rect.x + cursor_pos as u16 + 1)
.min(input_rect.x + input_rect.width.saturating_sub(2)),
input_rect.y + 1,
);
let input_text = &input_buffer.as_str();
let input_paragraph = Paragraph::new(*input_text)
.style(Style::default().fg(Color::Yellow))
.block(
Block::default()
.borders(Borders::ALL)
.title("[ Message Search ]"),
)
.scroll((
0,
((cursor_pos + 3) as u16).saturating_sub(input_rect.width),
));
frame.render_widget(Clear, input_rect);
frame.render_widget(input_paragraph, input_rect);
let all_messages = app
.messages_snapshot
.iter()
.flat_map(|f| match &f.payload {
PayLoad::Message(m) => Some(m.as_str()),
_ => None,
})
.collect::<VecDeque<&str>>();
if all_messages.is_empty() {
let window_paragraph = Table::new(vec![])
.block(Block::default().borders(Borders::ALL).title("[ Results ]"))
.column_spacing(2)
.style(styles::BORDER_NAME);
frame.render_widget(Clear, window_rect);
frame.render_widget(window_paragraph, window_rect);
return;
}
let maximum_message_length = *all_messages
.iter()
.map(|v| v.len())
.collect::<Vec<usize>>()
.iter()
.max()
.unwrap() as u16;
let table_widths = all_messages
.iter()
.map(|_| Constraint::Min(maximum_message_length))
.collect::<Vec<Constraint>>();
let render_messages = scroll_view(all_messages, app.scroll_offset, MAX_MESSAGE_SEARCH as usize);
let rows = if input_text.is_empty() {
render_messages
.iter()
.map(|&v| Row::new(vec![v]))
.collect::<Vec<Row>>()
} else {
render_messages
.iter()
.flat_map(|&f| {
let chars = f.chars();
if let Some((_, indices)) = FUZZY_FINDER.fuzzy_indices(f, input_text) {
Some(Row::new(vec![Spans::from(
chars
.enumerate()
.map(|(i, s)| {
if indices.contains(&i) {
Span::styled(
s.to_string(),
Style::default()
.fg(Color::Red)
.add_modifier(Modifier::BOLD),
)
} else {
Span::raw(s.to_string())
}
})
.collect::<Vec<Span>>(),
)]))
} else {
None
}
})
.collect::<Vec<Row>>()
};
let window_paragraph = Table::new(rows)
.block(Block::default().borders(Borders::ALL).title("[ Results ]"))
.column_spacing(2)
.widths(&table_widths)
.style(styles::BORDER_NAME);
frame.render_widget(Clear, window_rect);
frame.render_widget(window_paragraph, window_rect);
}

View File

@ -1,22 +1,40 @@
pub mod channels;
pub mod help; pub mod help;
pub mod messages;
use std::collections::VecDeque;
use tui::layout::{Constraint, Direction, Layout, Rect}; use tui::layout::{Constraint, Direction, Layout, Rect};
const HORIZONTAL_CONSTRAINTS: [Constraint; 3] = [
Constraint::Percentage(15),
Constraint::Percentage(70),
Constraint::Percentage(15),
];
pub enum Centering { pub enum Centering {
Input(u16, u16), Height(u16),
Window(u16, u16), Middle(u16),
} }
pub fn centered_popup(c: Centering, size: Rect) -> Rect { pub enum WindowType {
/// An input window, with the integer representing the height of the terminal
Input(u16),
/// A window containing either some specified terminal height, or in the middle,
/// with an integer describing the amount of vertically stored items
Window(Centering, u16),
}
pub fn centered_popup(c: WindowType, size: Rect) -> Rect {
match c { match c {
Centering::Window(percent_x, percent_y) => { WindowType::Input(v) => {
let popup_layout = Layout::default() let popup_layout = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints( .constraints(
[ [
Constraint::Percentage((100 - percent_y) / 2), Constraint::Length((v / 2) as u16 - 6),
Constraint::Percentage(percent_y), Constraint::Length(3),
Constraint::Percentage((100 - percent_y) / 2), Constraint::Min(0),
] ]
.as_ref(), .as_ref(),
) )
@ -24,40 +42,113 @@ pub fn centered_popup(c: Centering, size: Rect) -> Rect {
Layout::default() Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints( .constraints(HORIZONTAL_CONSTRAINTS.as_ref())
[
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
]
.as_ref(),
)
.split(popup_layout[1])[1] .split(popup_layout[1])[1]
} }
Centering::Input(percent_x, percent_y) => { WindowType::Window(v, i) => {
let popup_layout = Layout::default() let popup_layout = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints( .constraints([
[ Constraint::Length(match v {
Constraint::Percentage((100 - percent_y) / 2), Centering::Height(terminal_height) => (terminal_height / 2) as u16 - 3,
Constraint::Length(3), Centering::Middle(terminal_height) => ((terminal_height - i) / 2) as u16,
Constraint::Percentage((100 - percent_y) / 2), }),
] Constraint::Length(i),
.as_ref(), match v {
) Centering::Height(_) => Constraint::Min(0),
Centering::Middle(terminal_height) => {
Constraint::Length(((terminal_height - i) / 2) as u16 - 3)
}
},
])
.split(size); .split(size);
Layout::default() Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints( .constraints(HORIZONTAL_CONSTRAINTS.as_ref())
[
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
]
.as_ref(),
)
.split(popup_layout[1])[1] .split(popup_layout[1])[1]
} }
} }
} }
pub fn scroll_view<T: std::marker::Copy>(
v: VecDeque<T>,
offset: usize,
amount: usize,
) -> VecDeque<T> {
if offset > v.len() {
panic!(
"Scroll offset is {}, but length of VecDeque is {}.",
offset,
v.len()
);
}
// If the offset is at 0 or at the bottom of the input, then there's no need to move.
else if (offset == 0 && amount == v.len()) || v.is_empty() {
v
}
// If there's no amount specified, return everything behind and including the offset index values,
// or when the offset and the amount wanted goes over the length of the VecDeque.
else if amount == 0 || offset + amount > v.len() {
v.range(offset..).copied().collect::<VecDeque<T>>()
} else {
v.range(offset..offset + amount)
.copied()
.collect::<VecDeque<T>>()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn setup() -> VecDeque<i32> {
VecDeque::from([1, 2, 3, 4, 5])
}
#[test]
#[should_panic(expected = "Scroll offset is 10, but length of VecDeque is 5.")]
fn test_offset_plus_amount_over_length() {
scroll_view(setup(), 10, 3);
}
#[test]
#[should_panic(expected = "Scroll offset is 3, but length of VecDeque is 0.")]
fn test_zero_length_input_some_offset() {
scroll_view(vec![].iter().copied().collect::<VecDeque<i32>>(), 3, 0);
}
#[test]
fn test_zero_length_input_no_offset() {
let empty_deq: VecDeque<i32> = scroll_view(VecDeque::from([]), 0, 3);
assert_eq!(empty_deq, VecDeque::from([]));
}
#[test]
fn test_no_offset_no_amount() {
let empty_deq: VecDeque<i32> = scroll_view(VecDeque::from([]), 0, 0);
assert_eq!(empty_deq, VecDeque::from([]));
}
#[test]
fn test_offset_1_all_elements() {
assert_eq!(scroll_view(setup(), 1, 0), VecDeque::from([2, 3, 4, 5]));
}
#[test]
fn test_no_offset_some_amount() {
assert_eq!(scroll_view(setup(), 0, 3), VecDeque::from([1, 2, 3]));
}
#[test]
fn test_some_offset_some_amount() {
assert_eq!(scroll_view(setup(), 2, 2), VecDeque::from([3, 4]));
}
#[test]
fn test_offset_and_amount_centered() {
assert_eq!(scroll_view(setup(), 1, 3), VecDeque::from([2, 3, 4]));
}
}