mirror of
https://codeberg.org/icewind/logsmash.git
synced 2026-06-03 18:14:11 +02:00
start working on tui
This commit is contained in:
parent
5d0a447a21
commit
f9a1aa1415
12 changed files with 709 additions and 58 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,3 +2,4 @@ target
|
|||
.direnv
|
||||
.env
|
||||
result
|
||||
*.log
|
||||
425
Cargo.lock
generated
425
Cargo.lock
generated
|
|
@ -19,6 +19,18 @@ dependencies = [
|
|||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
|
|
@ -28,6 +40,12 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.3.2"
|
||||
|
|
@ -37,12 +55,24 @@ dependencies = [
|
|||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
|
|
@ -85,6 +115,21 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cassowary"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.6"
|
||||
|
|
@ -117,7 +162,7 @@ version = "4.1.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8d93d855ce6a0aa87b8473ef9169482f40abaa2e9e0993024c35c902cbd5920"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 1.3.2",
|
||||
"clap_derive",
|
||||
"clap_lex",
|
||||
"is-terminal",
|
||||
|
|
@ -132,7 +177,7 @@ version = "4.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -157,6 +202,7 @@ dependencies = [
|
|||
"itertools",
|
||||
"log",
|
||||
"main_error",
|
||||
"ratatui",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
@ -171,6 +217,19 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
|
||||
dependencies = [
|
||||
"castaway",
|
||||
"cfg-if",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.0"
|
||||
|
|
@ -216,6 +275,31 @@ version = "0.8.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
|
|
@ -322,6 +406,10 @@ name = "hashbrown"
|
|||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
|
|
@ -329,6 +417,12 @@ version = "0.4.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
|
|
@ -371,7 +465,7 @@ checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
|
|||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -404,6 +498,16 @@ version = "0.2.155"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lockfree-object-pool"
|
||||
version = "0.1.6"
|
||||
|
|
@ -416,6 +520,15 @@ version = "0.4.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lzma-rs"
|
||||
version = "0.3.0"
|
||||
|
|
@ -447,6 +560,18 @@ dependencies = [
|
|||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
|
|
@ -465,6 +590,35 @@ version = "6.6.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.2"
|
||||
|
|
@ -565,6 +719,36 @@ dependencies = [
|
|||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cassowary",
|
||||
"compact_str",
|
||||
"crossterm",
|
||||
"itertools",
|
||||
"lru",
|
||||
"paste",
|
||||
"stability",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"unicode-segmentation",
|
||||
"unicode-truncate",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.5"
|
||||
|
|
@ -594,12 +778,24 @@ version = "0.8.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.204"
|
||||
|
|
@ -642,18 +838,92 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "stability"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
|
|
@ -742,6 +1012,29 @@ version = "1.0.12"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-truncate"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
|
@ -754,13 +1047,44 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -769,7 +1093,22 @@ version = "0.52.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -778,28 +1117,46 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
|
|
@ -812,30 +1169,74 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.1"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
name = "cloud-log-analyser"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.74.0"
|
||||
|
||||
[dependencies]
|
||||
main_error = "0.1.2"
|
||||
|
|
@ -14,6 +15,7 @@ clap = { version = "=4.1.3", features = ["derive"] }
|
|||
cloud-log-analyser-data = { version = "0.1.0", path = "./data" }
|
||||
zip = "2.1.5"
|
||||
itertools = "0.13.0"
|
||||
ratatui = "0.27.0"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
|
@ -94,36 +94,63 @@ impl From<&LoggingStatementWithPathPrefix> for LoggingStatement {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for LoggingStatementWithPathPrefix {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(exception) = self.exception {
|
||||
write!(
|
||||
f,
|
||||
"{}({}): {}{} line {}",
|
||||
exception,
|
||||
self.message(),
|
||||
self.path_prefix,
|
||||
self.path,
|
||||
self.line
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{}: {}{} line {}",
|
||||
self.message(),
|
||||
self.path_prefix,
|
||||
self.path,
|
||||
self.line
|
||||
)
|
||||
impl LoggingStatementWithPathPrefix {
|
||||
fn raw_message(&self) -> LoggingMessage {
|
||||
LoggingMessage {
|
||||
message: self.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> impl Display {
|
||||
LoggingStatementPath {
|
||||
path_prefix: self.path_prefix,
|
||||
path: self.path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message(&self) -> impl Display {
|
||||
LoggingStatementMessage {
|
||||
message: self.raw_message(),
|
||||
exception: self.exception,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LoggingStatementWithPathPrefix {
|
||||
pub fn message(&self) -> impl Display + '_ {
|
||||
LoggingMessage {
|
||||
message: self.into(),
|
||||
struct LoggingStatementPath {
|
||||
pub path_prefix: &'static str,
|
||||
pub path: &'static str,
|
||||
}
|
||||
|
||||
impl Display for LoggingStatementPath {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}{}", self.path_prefix, self.path,)
|
||||
}
|
||||
}
|
||||
|
||||
struct LoggingStatementMessage {
|
||||
pub message: LoggingMessage,
|
||||
pub exception: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl Display for LoggingStatementMessage {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(exception) = self.exception {
|
||||
write!(f, "«{}({})»", exception, self.message)
|
||||
} else {
|
||||
write!(f, "«{}»", self.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LoggingStatementWithPathPrefix {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"«{}» {} line {}",
|
||||
self.raw_message(),
|
||||
self.path(),
|
||||
self.line
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@
|
|||
releaseMatrix = buildMatrix releaseTargets;
|
||||
|
||||
devShells.default = mkShell {
|
||||
nativeBuildInputs = with pkgs; [cargo rustc bacon cargo-msrv cargo-insta clippy];
|
||||
nativeBuildInputs = with pkgs; [msrvToolchain rustc bacon cargo-msrv cargo-insta];
|
||||
};
|
||||
}
|
||||
)
|
||||
|
|
|
|||
12
src/app.rs
Normal file
12
src/app.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
use crate::matcher::MatchResult;
|
||||
use cloud_log_analyser_data::StatementList;
|
||||
|
||||
pub struct App {
|
||||
pub log_statements: StatementList,
|
||||
pub matches: Vec<LogMatch>,
|
||||
}
|
||||
|
||||
pub struct LogMatch {
|
||||
pub result: MatchResult,
|
||||
pub count: usize,
|
||||
}
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
use thiserror::Error;
|
||||
use zip::result::ZipError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum UiError {
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error(transparent)]
|
||||
Log(#[from] LogError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LogError {
|
||||
#[error("Error while reading input file '{path}': {err:#}")]
|
||||
|
|
|
|||
30
src/main.rs
30
src/main.rs
|
|
@ -1,7 +1,9 @@
|
|||
use crate::app::{App, LogMatch};
|
||||
use crate::error::LogError;
|
||||
use crate::logfile::LogFile;
|
||||
use crate::logline::LogLine;
|
||||
use crate::matcher::{MatchResult, Matcher};
|
||||
use crate::ui::run_ui;
|
||||
use clap::Parser;
|
||||
use cloud_log_analyser_data::{get_statements, MAX_VERSION};
|
||||
use main_error::MainResult;
|
||||
|
|
@ -9,10 +11,12 @@ use std::collections::HashMap;
|
|||
use std::iter::once;
|
||||
use std::ops::AddAssign;
|
||||
|
||||
mod app;
|
||||
mod error;
|
||||
mod logfile;
|
||||
mod logline;
|
||||
mod matcher;
|
||||
mod ui;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Args {
|
||||
|
|
@ -66,25 +70,19 @@ fn main() -> MainResult {
|
|||
}
|
||||
}
|
||||
|
||||
if args.unmatched {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut counts: Vec<(_, _)> = counts.into_iter().collect();
|
||||
counts.sort_by_key(|(_, count)| *count);
|
||||
counts.reverse();
|
||||
for (match_result, count) in counts {
|
||||
println!("{}: {}", match_result.display(&statements), count);
|
||||
}
|
||||
if unmatched_total > 0 {
|
||||
eprintln!("\n{unmatched_total} lines couldn't be matched:");
|
||||
for (app, count) in unmatched_counts {
|
||||
eprintln!("\t{app}: {count}");
|
||||
}
|
||||
}
|
||||
if error_count > 0 {
|
||||
eprintln!("\n{error_count} lines failed to parse as valid log json");
|
||||
}
|
||||
|
||||
let app = App {
|
||||
log_statements: statements,
|
||||
matches: counts
|
||||
.into_iter()
|
||||
.map(|(result, count)| LogMatch { result, count })
|
||||
.collect(),
|
||||
};
|
||||
|
||||
run_ui(app)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use crate::logline::LogLine;
|
||||
use cloud_log_analyser_data::{LogLevel, LoggingStatement, StatementList};
|
||||
use itertools::Either;
|
||||
use regex::Regex;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::iter::once;
|
||||
|
||||
pub struct LogMatch {
|
||||
level: LogLevel,
|
||||
|
|
@ -90,15 +92,35 @@ pub enum MatchResult {
|
|||
}
|
||||
|
||||
impl MatchResult {
|
||||
pub fn display<'a>(&'a self, log_statements: &'a StatementList) -> impl Display + 'a {
|
||||
pub fn display<'a>(
|
||||
&'a self,
|
||||
log_statements: &'a StatementList,
|
||||
max_length: usize,
|
||||
) -> impl Display + 'a {
|
||||
MatchResultDisplay {
|
||||
max_length,
|
||||
log_statements,
|
||||
result: &self,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
MatchResult::Single(_) => 1,
|
||||
MatchResult::List(list) => list.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {
|
||||
match self {
|
||||
MatchResult::Single(index) => Either::Left(once(*index)),
|
||||
MatchResult::List(list) => Either::Right(list.iter().copied()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MatchResultDisplay<'a> {
|
||||
max_length: usize,
|
||||
log_statements: &'a StatementList,
|
||||
result: &'a MatchResult,
|
||||
}
|
||||
|
|
@ -110,17 +132,20 @@ impl Display for MatchResultDisplay<'_> {
|
|||
if let Some(statement) = self.log_statements.get(*index) {
|
||||
write!(f, "{statement}")
|
||||
} else {
|
||||
write!(f, "unknown statement")
|
||||
write!(f, "«unknown statement»")
|
||||
}
|
||||
}
|
||||
MatchResult::List(list) => {
|
||||
writeln!(f, "{} possible matches:", list.len())?;
|
||||
for index in list {
|
||||
// todo: max length
|
||||
for (i, index) in list.iter().enumerate() {
|
||||
if let Some(statement) = self.log_statements.get(*index) {
|
||||
writeln!(f, " {statement}")?;
|
||||
if i > 0 {
|
||||
write!(f, " or ")?;
|
||||
}
|
||||
write!(f, "{statement}\n")?;
|
||||
}
|
||||
}
|
||||
write!(f, " ")
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
50
src/ui/match_list.rs
Normal file
50
src/ui/match_list.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use crate::app::{App, LogMatch};
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::style::palette::tailwind;
|
||||
use ratatui::widgets::{Cell, HighlightSpacing, Row, Table};
|
||||
use std::fmt::Write;
|
||||
|
||||
pub fn match_list(app: &App) -> Table {
|
||||
let header_style = Style::default()
|
||||
.bg(tailwind::BLACK)
|
||||
.fg(tailwind::GREEN.c600);
|
||||
let selected_style = Style::default()
|
||||
.add_modifier(Modifier::REVERSED)
|
||||
.bg(tailwind::BLACK)
|
||||
.fg(tailwind::GREEN.c600);
|
||||
|
||||
let header = ["Statement", "File", "Line", "Count"]
|
||||
.into_iter()
|
||||
.map(Cell::from)
|
||||
.collect::<Row>()
|
||||
.style(header_style)
|
||||
.height(1);
|
||||
|
||||
let widths = [
|
||||
Constraint::Percentage(60),
|
||||
Constraint::Percentage(40),
|
||||
Constraint::Min(10),
|
||||
Constraint::Min(10),
|
||||
];
|
||||
let table = Table::new(
|
||||
app.matches.iter().map(|result| log_row(result, app)),
|
||||
widths,
|
||||
)
|
||||
.header(header)
|
||||
.highlight_style(selected_style)
|
||||
.highlight_spacing(HighlightSpacing::Always);
|
||||
table
|
||||
}
|
||||
|
||||
fn log_row<'a>(result: &LogMatch, app: &'a App) -> Row<'a> {
|
||||
let mut message = String::new();
|
||||
let mut paths = String::new();
|
||||
let mut lines = String::new();
|
||||
for index in result.result.iter() {
|
||||
let statement = app.log_statements.get(index).expect("invalid match index");
|
||||
writeln!(&mut message, "{}", statement.message()).unwrap();
|
||||
writeln!(&mut paths, "{}", statement.path()).unwrap();
|
||||
writeln!(&mut lines, "{}", statement.line).unwrap();
|
||||
}
|
||||
Row::new([message, paths, lines, result.count.to_string()]).height(result.result.len() as u16)
|
||||
}
|
||||
64
src/ui/mod.rs
Normal file
64
src/ui/mod.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
use crate::app::App;
|
||||
use crate::error::UiError;
|
||||
use crate::ui::match_list::match_list;
|
||||
use crate::ui::state::{UiEvent, UiState};
|
||||
use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers};
|
||||
use ratatui::crossterm::terminal::{
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
};
|
||||
use ratatui::crossterm::{event, ExecutableCommand};
|
||||
use ratatui::prelude::*;
|
||||
use ratatui::Terminal;
|
||||
use std::io;
|
||||
use std::io::stdout;
|
||||
|
||||
mod match_list;
|
||||
mod state;
|
||||
|
||||
pub fn run_ui(app: App) -> Result<(), UiError> {
|
||||
enable_raw_mode()?;
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
|
||||
|
||||
let mut ui_state = UiState::default();
|
||||
|
||||
while !matches!(ui_state, UiState::Quit) {
|
||||
terminal.draw(|frame| ui(frame, &app, &mut ui_state))?;
|
||||
if let Some(event) = handle_events()? {
|
||||
ui_state = ui_state.process(event, &app);
|
||||
}
|
||||
}
|
||||
|
||||
disable_raw_mode()?;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_events() -> io::Result<Option<UiEvent>> {
|
||||
if event::poll(std::time::Duration::from_millis(50))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if key.kind == event::KeyEventKind::Press {
|
||||
return Ok(match key.code {
|
||||
KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => {
|
||||
Some(UiEvent::Quit)
|
||||
}
|
||||
KeyCode::Char('q') => Some(UiEvent::Quit),
|
||||
KeyCode::Esc => Some(UiEvent::Back),
|
||||
KeyCode::Down => Some(UiEvent::Down),
|
||||
KeyCode::Up => Some(UiEvent::Up),
|
||||
_ => None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
||||
match state {
|
||||
UiState::Quit => {}
|
||||
UiState::MatchList { table_state } => {
|
||||
frame.render_stateful_widget(match_list(app), frame.size(), table_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/ui/state.rs
Normal file
63
src/ui/state.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
use crate::app::App;
|
||||
use ratatui::widgets::TableState;
|
||||
use table_state::TableStateExt;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum UiState {
|
||||
MatchList { table_state: TableState },
|
||||
Quit,
|
||||
}
|
||||
|
||||
impl Default for UiState {
|
||||
fn default() -> Self {
|
||||
let mut table_state = TableState::default();
|
||||
table_state.select(Some(0));
|
||||
UiState::MatchList { table_state }
|
||||
}
|
||||
}
|
||||
|
||||
impl UiState {
|
||||
pub fn process(self, event: UiEvent, app: &App) -> UiState {
|
||||
match (self, event) {
|
||||
(UiState::Quit, _) => UiState::Quit,
|
||||
(_, UiEvent::Quit) => UiState::Quit,
|
||||
(UiState::MatchList { .. }, UiEvent::Back) => UiState::Quit,
|
||||
(UiState::MatchList { mut table_state }, UiEvent::Down) => {
|
||||
table_state.down(app.matches.len());
|
||||
UiState::MatchList { table_state }
|
||||
}
|
||||
(UiState::MatchList { mut table_state }, UiEvent::Up) => {
|
||||
table_state.up(app.matches.len());
|
||||
UiState::MatchList { table_state }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum UiEvent {
|
||||
Quit,
|
||||
Back,
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
mod table_state {
|
||||
use ratatui::widgets::TableState;
|
||||
|
||||
pub trait TableStateExt {
|
||||
fn up(&mut self, count: usize);
|
||||
fn down(&mut self, count: usize);
|
||||
}
|
||||
|
||||
impl TableStateExt for TableState {
|
||||
fn up(&mut self, count: usize) {
|
||||
let current = self.selected().unwrap_or(0);
|
||||
self.select(Some(if current == 0 { count - 1 } else { current - 1 }))
|
||||
}
|
||||
|
||||
fn down(&mut self, count: usize) {
|
||||
let current = self.selected().unwrap_or(0);
|
||||
self.select(Some((current + 1).rem_euclid(count)))
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue