diff --git a/.github/release-body.md b/.github/release-body.md index da05c7b..fe9b3e0 100644 --- a/.github/release-body.md +++ b/.github/release-body.md @@ -1,8 +1,18 @@ -### 2025-08-21 +### 2025-09-28 -### Reverts -+ GitHub workflow reverted, [d1b69858c622636afbc46db7d37cf91e58d61212] +### Chores ++ create_release.sh updated, [d4af754ad245540db60177f7b202b3c64519c961] ++ dependencies updated, [03599b46657d38d0c9f25c2ccfd9510f2b98dd84], [aef0c9503e7045a256856aa887d8c8d7722b9936], [f0771eab5d07d141fe7a8997db650f0f65ffe0a7], [1596de8681ad6c0a7832eb922dd2dc36ab30eb41] ++ GitHub workflow updated, [66dae5e61ea294ac8ce134a6c32b27c04166b6eb] -see [v0.11.0 release notes](https://github.com/mrjackwills/oxker/releases/tag/v0.11.0) for more information about v0.11 release +### Docs ++ fix numerous typos, [618a43b501914fdf2659e171172ad180364cf87a] + +### Features ++ *BREAKING CHANGE* - `scroll_down_many` & `scroll_up_many` removed, `scroll_down_one` `scroll_up_one` renamed `scroll_down`, `scroll_up`, see [example_config](https://github.com/mrjackwills/oxker/tree/main/example_config), [52a04ec1d0b9e4877e304f60a857ebc00f88b4fd] ++ log search feature, closes #72. Use `#` button, remappable via `log_search_mode`, to enter log search mode. Case-sensitive by default, editable in `config.toml` with `log_search_case_sensitive` entry. Customise colours via `[colors.log_search]` entries, again see see [example_config](https://github.com/mrjackwills/oxker/tree/main/example_config), [96d9469623a7c90b79aa8d82abf587290343ad37], [a2316a9cac270790920a1ebd1be6532d51aba77c] ++ `term` renamed `filter term`, tests updated, [487c3faf96f4c197c8b82644c02466ea40626a5e] + +My 32-bit armhf armv6 hardware no longer seems to be able to run Docker. Future `oxker` releases won't be tested on real hardware but will continue to be built and published for armv6. see CHANGELOG.md for more details diff --git a/.github/workflows/create_release_and_build.yml b/.github/workflows/create_release_and_build.yml index dc5ad2c..c737331 100644 --- a/.github/workflows/create_release_and_build.yml +++ b/.github/workflows/create_release_and_build.yml @@ -1,10 +1,16 @@ name: Release CI + +permissions: + contents: write + packages: write + on: push: tags: - "v[0-9]+.[0-9]+.[0-9]+" jobs: + ################################################# ## Cross platform binary build for release page # ################################################# @@ -98,6 +104,7 @@ jobs: artifacts: | **/oxker_*.zip **/oxker_*.tar.gz + ######################################### ## Build images for Dockerhub & ghcr.io # ######################################### @@ -169,4 +176,3 @@ jobs: - uses: actions/checkout@v5 - name: Publish run: cargo publish - diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a77596..633a832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# v0.12.0 +### 2025-09-28 + +### Chores ++ create_release.sh updated, [d4af754a](https://github.com/mrjackwills/oxker/commit/d4af754ad245540db60177f7b202b3c64519c961) ++ dependencies updated, [03599b46](https://github.com/mrjackwills/oxker/commit/03599b46657d38d0c9f25c2ccfd9510f2b98dd84), [aef0c950](https://github.com/mrjackwills/oxker/commit/aef0c9503e7045a256856aa887d8c8d7722b9936), [f0771eab](https://github.com/mrjackwills/oxker/commit/f0771eab5d07d141fe7a8997db650f0f65ffe0a7), [1596de86](https://github.com/mrjackwills/oxker/commit/1596de8681ad6c0a7832eb922dd2dc36ab30eb41) ++ GitHub workflow updated, [66dae5e6](https://github.com/mrjackwills/oxker/commit/66dae5e61ea294ac8ce134a6c32b27c04166b6eb) + +### Docs ++ fix numerous typos, [618a43b5](https://github.com/mrjackwills/oxker/commit/618a43b501914fdf2659e171172ad180364cf87a) + +### Features ++ *BREAKING CHANGE* - `scroll_down_many` & `scroll_up_many` removed, `scroll_down_one` `scroll_up_one` renamed `scroll_down`, `scroll_up`, see [example_config](https://github.com/mrjackwills/oxker/tree/main/example_config), [52a04ec1](https://github.com/mrjackwills/oxker/commit/52a04ec1d0b9e4877e304f60a857ebc00f88b4fd) ++ log search feature, closes [#72](https://github.com/mrjackwills/oxker/issues/72). Use `#` button, remappable via `log_search_mode`, to enter log search mode. Case-sensitive by default, editable in `config.toml` with `log_search_case_sensitive` entry. Customise colours via `[colors.log_search]` entries, again see see [example_config](https://github.com/mrjackwills/oxker/tree/main/example_config), [96d94696](https://github.com/mrjackwills/oxker/commit/96d9469623a7c90b79aa8d82abf587290343ad37), [a2316a9c](https://github.com/mrjackwills/oxker/commit/a2316a9cac270790920a1ebd1be6532d51aba77c) ++ `term` renamed `filter term`, tests updated, [487c3faf](https://github.com/mrjackwills/oxker/commit/487c3faf96f4c197c8b82644c02466ea40626a5e) + +My 32-bit armhf armv6 hardware no longer seems to be able to run Docker. Future `oxker` releases won't be tested on real hardware but will continue to be built and published for armv6. + # v0.11.1 ### 2025-08-21 @@ -17,7 +35,7 @@ see [v0.11.0 release notes](https://github.com/mrjackwills/oxker/releases/tag/v0 ### Features + Horizontally scroll across logs. By default use `←` & `→` keys to traverse horizontally across the lines when logs panel selected. Updated `config.toml` with `log_scroll_forward` and `log_scroll_back` [c190f020](https://github.com/mrjackwills/oxker/commit/c190f0206cc55b8e45b8373f9be954e828c18b3b), [8939ac03](https://github.com/mrjackwills/oxker/commit/8939ac0345326633e794cc10a981a1f3c5c07549) + Force clear screen & redraw of UI. By default uses `f` key, `config.toml` updated with `force_redraw` [50edbc0c](https://github.com/mrjackwills/oxker/commit/50edbc0cc09db864835fe81a03cba8eadafe548b) -+ Increase scroll speed using the `ctrl` key in conjuction with a scroll key, `config.toml` updated with `scroll_modifier`. The next release will remove `scroll_down_many` & `scroll_down_up` keys, [c5bbffdb](https://github.com/mrjackwills/oxker/commit/c5bbffdb5f9e800951e4060aa6aee8e00db589aa) ++ Increase scroll speed using the `ctrl` key in conjunction with a scroll key, `config.toml` updated with `scroll_modifier`. The next release will remove `scroll_down_many` & `scroll_down_up` keys, [c5bbffdb](https://github.com/mrjackwills/oxker/commit/c5bbffdb5f9e800951e4060aa6aee8e00db589aa) ### Refactors + remove macos cfg none-const functions, Zigbuild now uses Rust 1.87.0, [eb686e2c](https://github.com/mrjackwills/oxker/commit/eb686e2c952e04da74b3e12c0bfa015ec4615e1d) diff --git a/Cargo.lock b/Cargo.lock index 32bb4de..f89e920 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] @@ -23,12 +23,6 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -90,9 +84,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "atomic-waker" @@ -108,9 +102,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -118,7 +112,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -129,9 +123,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.2" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "bollard" @@ -213,10 +207,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.33" +version = "1.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" +checksum = "e1354349954c6fc9cb0deab020f27f783cf0b604e8bb754dc4658ecf0d29c35f" dependencies = [ + "find-msvc-tools", "shlex", ] @@ -228,11 +223,10 @@ checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "num-traits", "serde", @@ -241,9 +235,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.45" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", "clap_derive", @@ -251,9 +245,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.44" +version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstream", "anstyle", @@ -265,9 +259,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.45" +version = "4.5.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" dependencies = [ "heck", "proc-macro2", @@ -356,7 +350,7 @@ dependencies = [ "document-features", "mio", "parking_lot", - "rustix 1.0.8", + "rustix 1.1.2", "signal-hook", "signal-hook-mio", "winapi", @@ -408,12 +402,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -455,7 +449,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] [[package]] @@ -504,14 +498,20 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.1", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959" + [[package]] name = "fnv" version = "1.0.7" @@ -605,14 +605,14 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasi 0.14.7+wasi-0.2.4", ] [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "hashbrown" @@ -631,6 +631,12 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + [[package]] name = "heck" version = "0.5.0" @@ -728,9 +734,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "bytes", "futures-channel", @@ -764,9 +770,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -912,13 +918,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.15.5", + "hashbrown 0.16.0", "serde", + "serde_core", ] [[package]] @@ -929,9 +936,9 @@ checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "insta" -version = "1.43.1" +version = "1.43.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371" +checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0" dependencies = [ "console", "once_cell", @@ -953,9 +960,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ "bitflags", "cfg-if", @@ -1027,9 +1034,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -1043,15 +1050,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags", "libc", @@ -1065,9 +1072,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -1093,9 +1100,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru" @@ -1108,9 +1115,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "miniz_oxide" @@ -1135,12 +1142,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" dependencies = [ - "overload", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -1160,9 +1166,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -1185,15 +1191,9 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "oxker" -version = "0.11.1" +version = "0.12.0" dependencies = [ "anyhow", "bollard", @@ -1281,9 +1281,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] @@ -1438,15 +1438,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.1", ] [[package]] @@ -1493,18 +1493,28 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1513,14 +1523,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -1547,11 +1558,11 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -1568,15 +1579,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.0" +version = "3.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.11.4", "schemars 0.9.0", "schemars 1.0.4", "serde", @@ -1751,9 +1762,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -1766,15 +1777,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -1836,11 +1847,11 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" dependencies = [ - "serde", + "serde_core", "serde_spanned", "toml_datetime", "toml_parser", @@ -1849,18 +1860,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" dependencies = [ "winnow", ] @@ -1916,9 +1927,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "nu-ansi-term", "sharded-slab", @@ -1942,9 +1953,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "unicode-segmentation" @@ -1977,13 +1988,14 @@ checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "url" -version = "2.5.6" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "137a3c834eaf7139b73688502f3f1141a0337c5d8e4d9b536f9b8c796e26a7c4" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -2000,9 +2012,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -2033,30 +2045,40 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.14.2+wasi-0.2.4" +version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ - "wit-bindgen-rt", + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", @@ -2068,9 +2090,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2078,9 +2100,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", @@ -2091,9 +2113,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] @@ -2122,9 +2144,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "6844ee5416b285084d3d3fffd743b925a6c9385455f64f6d4fa3031c4c2749a9" dependencies = [ "windows-implement", "windows-interface", @@ -2135,9 +2157,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "edb307e42a74fb6de9bf3a02d9712678b22399c87e6fa869d6dfcd8c1b7754e0" dependencies = [ "proc-macro2", "quote", @@ -2146,9 +2168,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "c0abd1ddbc6964ac14db11c7213d6532ef34bd9aa042c2e5935f59d7908b46a5" dependencies = [ "proc-macro2", "quote", @@ -2157,28 +2179,37 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" [[package]] name = "windows-result" -version = "0.3.4" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "7084dcc306f89883455a206237404d3eaf961e5bd7e0f312f7c91f57eb44167f" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7218c655a553b0bed4426cf54b20d7ba363ef543b52d515b3e48d7fd55318dda" dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -2194,7 +2225,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.4", +] + +[[package]] +name = "windows-sys" +version = "0.61.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f109e41dd4a3c848907eb83d5a42ea98b3769495597450cf6d153507b166f0f" +dependencies = [ + "windows-link", ] [[package]] @@ -2215,9 +2255,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "2d42b7b7f66d2a06854650af09cfdf8713e427a439c97ad65a6375318033ac4b" dependencies = [ "windows-link", "windows_aarch64_gnullvm 0.53.0", @@ -2328,18 +2368,15 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -2373,18 +2410,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e86ae27..ed71efa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "oxker" -version = "0.11.1" +version = "0.12.0" edition = "2024" authors = ["Jack Wills "] description = "A simple tui to view & control docker containers" diff --git a/README.md b/README.md index b5f36c5..7aaaa01 100644 --- a/README.md +++ b/README.md @@ -110,13 +110,14 @@ In application controls, these, amongst many other settings, can be customized w | button| result| |--|--| | ```( tab )``` or ```( shift+tab )``` | Change panel, clicking on a panel also changes the selected panel.| -| ```( ↑ ↓ )``` or ```( j k )``` or ```( PgUp PgDown )``` or ```( Home End )```| Scroll line in selected panel - mouse wheel will also scroll.| +| ```( ↑ ↓ )``` or ```( j k )``` or ```( Home End )```| Scroll line in selected panel - mouse wheel will also scroll.| | ```( ← → )``` | When logs panel selected, scroll horizontally across the text of the logs.| -| ```( ctrl )``` | Increase scroll speed, used in conjuction with scroll keys.| +| ```( ctrl )``` | Increase scroll speed, used in conjunction with scroll keys.| | ```( enter )```| Run selected docker command.| | ```( 1-9 )``` | Sort containers by heading, clicking on headings also sorts the selected column. | | ```( 0 )``` | Stop sorting.| | ```( F1 )``` or ```( / )``` | Enter filter mode. | +| ```( # )``` | Enter log search mode. | | ```( - ) ``` or ```(=)``` | Reduce or increase the height of the logs panel.| | ```( \ )``` | Toggle the visibility of the logs panel.| | ```( e )``` | Exec into the selected container - not available on Windows.| diff --git a/_typos.toml b/_typos.toml index 6fd8cd3..372dc95 100644 --- a/_typos.toml +++ b/_typos.toml @@ -1,5 +1,2 @@ [default.extend-words] ratatui = "ratatui" - -[default] -extend-ignore-words-re = ["[(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{40})]"] diff --git a/create_release.sh b/create_release.sh index 16471d4..8b50e64 100755 --- a/create_release.sh +++ b/create_release.sh @@ -1,7 +1,7 @@ #!/bin/bash -# rust create_release v0.6.2 -# 2025-02-22 +# rust create_release v0.6.3 +# 2025-09-20 STAR_LINE='****************************************' CWD=$(pwd) @@ -240,20 +240,29 @@ zig_build_aarch64_apple() { fi } +cargo_clean() { + echo -e "${YELLOW}cargo clean${RESET}" + cargo clean +} + # Build all releases that GitHub workflow would # This will download GB's of docker images +# $1 is 0 or 1, if 1 won't run ask_continue cross_build_all() { - cargo clean + if ask_yn "cargo clean"; then + cargo_clean + fi + skip_confirm=$1 cross_build_armv6_linux - ask_continue + [ "$skip_confirm" -ne 1 ] && ask_continue cross_build_aarch64_linux - ask_continue + [ "$skip_confirm" -ne 1 ] && ask_continue cross_build_x86_linux - ask_continue + [ "$skip_confirm" -ne 1 ] && ask_continue cross_build_x86_windows - ask_continue + [ "$skip_confirm" -ne 1 ] && ask_continue zig_build_aarch64_apple - ask_continue + [ "$skip_confirm" -ne 1 ] && ask_continue } # $1 text to colourise @@ -304,13 +313,15 @@ build_container_armv6() { } # Build all the containers, this get executed in the github action +# $1 is 0 or 1, if 1 won't run ask_continue build_container_all() { + skip_confirm=$1 build_container_amd64 - ask_continue + [ "$skip_confirm" -ne 1 ] && ask_continue build_container_arm64 - ask_continue + [ "$skip_confirm" -ne 1 ] && ask_continue build_container_armv6 - ask_continue + [ "$skip_confirm" -ne 1 ] && ask_continue } # Full flow to create a new release @@ -322,8 +333,8 @@ release_flow() { get_git_remote_url cargo_test - cross_build_all - build_container_all + cross_build_all 0 + build_container_all 0 cargo_publish_dry_run cd "${CWD}" || error_close "Can't find ${CWD}" @@ -394,6 +405,7 @@ build_choice() { 4 "aarch64 apple" off 5 "x86 windows" off 6 "all" off + 7 "all automatic" off ) choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) exitStatus=$? @@ -426,7 +438,11 @@ build_choice() { exit ;; 6) - cross_build_all + cross_build_all 0 + exit + ;; + 7) + cross_build_all 1 exit ;; esac @@ -440,6 +456,7 @@ build_container_choice() { 2 "aarch64" off 3 "armv6" off 4 "all" off + 5 "all automatic" off ) choices=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) exitStatus=$? @@ -464,7 +481,11 @@ build_container_choice() { exit ;; 4) - build_container_all + build_container_all 0 + exit + ;; + 5) + build_container_all 1 exit ;; esac diff --git a/example_config/example.config.jsonc b/example_config/example.config.jsonc index 8abb678..3b1f8fd 100644 --- a/example_config/example.config.jsonc +++ b/example_config/example.config.jsonc @@ -31,6 +31,8 @@ "use_cli": false, // Show the logs section - this can be changed during operation with the log_section_toggle key "show_logs": true, + // Use case-sensitive matching for logs + "log_search_case_sensitive": true, ////////////////// // Custom Keymap // ////////////////// @@ -67,6 +69,10 @@ "/", "F1" ], + // Enter log search mode + "log_search_mode": [ + "#" + ], // Quit at anytime "quit": [ "q" @@ -75,14 +81,8 @@ "save_logs": [ "s" ], - // TODO "scroll_down_many" will be removed in the next release - // Scroll down a list by many - "scroll_down_many": [ - "pagedown" - ], - // TODO rename in next release // Scroll down a list by one item - "scroll_down_one": [ + "scroll_down": [ "down", "j" ], @@ -90,20 +90,16 @@ "scroll_end": [ "end" ], - // Modifier to scroll by 10 lines isntead of one, used in conjunction with scroll_up_x/scroll_down_x - "scroll_many": ["control"], + // Modifier to scroll by 10 lines instead of one, used in conjunction with scroll_up/scroll_down + "scroll_many": [ + "control" + ], // Scroll up to the start of a list "scroll_start": [ "home" ], - // TODO "scroll_up_many" will be removed in the next release - // Scroll up a list by many - "scroll_up_many": [ - "pageup" - ], - // TODO rename in next release // Scroll up a list by one item - "scroll_up_one": [ + "scroll_up": [ "up", "k" ], @@ -297,6 +293,17 @@ // Highlighted text color "highlight": "magenta" }, + // The log search panel + "log_search": { + // Background color of panel + "background": "reset", + // color of text + "text": "gray", + // text color of the buttons text + "button_text": "black", + // Highlighted text color + "highlight": "magenta" + }, // The logs panel, will only be applied if color_logs is false "logs": { // Background color of panel diff --git a/example_config/example.config.toml b/example_config/example.config.toml index 182e7c5..8d5f1db 100644 --- a/example_config/example.config.toml +++ b/example_config/example.config.toml @@ -43,6 +43,9 @@ use_cli = false # Show the logs section - this can be changed during operation with the log_section_toggle key show_logs = true +# Use case-sensitive matching for logs +log_search_case_sensitive = true + ################# # Custom Keymap # ################# @@ -72,29 +75,25 @@ delete_confirm = ["y"] exec = ["e"] # Enter filter mode filter_mode = ["/", "F1"] + +# Enter log search mode +log_search_mode = ["#"] + # Quit at anytime quit = ["q"] # Save logs of selected container to file on disk save_logs = ["s"] -# TODO "scroll_down_many" will be removed in the next release -# scroll down a list by many -scroll_down_many = ["pagedown"] -# TODO rename in next release # scroll down a list by one item -scroll_down_one = ["down", "j"] +scroll_down = ["down", "j"] # scroll down to the end of a list scroll_end = ["end"] -# Modifier to scroll by 10 lines isntead of one, used in conjunction with scroll_up_x/scroll_down_x +# Modifier to scroll by 10 lines instead of one, used in conjunction with scroll_up/scroll_down scroll_many = ["control"] # scroll up to the start of a list scroll_start = ["home"] -# TODO "scroll_up_many" will be removed in the next release -# scroll up a list by many -scroll_up_many = ["pageup"] -# TODO rename in next release # scroll up a list by one item -scroll_up_one = ["up", "k"] +scroll_up = ["up", "k"] # Horizontal scroll of the logs log_scroll_forward = ["right"] log_scroll_back = ["left"] @@ -123,6 +122,9 @@ log_section_height_decrease = ["-"] log_section_height_increase = ["+"] # Toggle visibility of the log section log_section_toggle = ["\\"] + + + # Force a complete clear & redraw of the screen force_redraw = ["f"] @@ -200,6 +202,17 @@ selected_filter_text = "black" highlight = "magenta" +# The log search panel +[colors.log_search] +# Background color of panel +background = "reset" +# color of text +text = "gray" +# text color of the buttons text +button_text = "black" +# Highlighted text color +highlight = "magenta" + # The color the of Docker commands available for each container [colors.commands] # Background color of panel diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index d31550e..11e39b3 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -22,6 +22,12 @@ const ONE_KB: f64 = 1000.0; const ONE_MB: f64 = ONE_KB * 1000.0; const ONE_GB: f64 = ONE_MB * 1000.0; +#[derive(Debug, Clone, Eq, Hash, PartialEq)] +pub enum ScrollDirection { + Next, + Previous, +} + #[derive(Debug, Clone, Eq, Hash, PartialEq)] pub struct ContainerId(String); @@ -177,7 +183,14 @@ impl StatefulList { self.state.select(Some(0)); } - pub fn next(&mut self) { + pub fn scroll(&mut self, scroll: &ScrollDirection) { + match scroll { + ScrollDirection::Next => self.next(), + ScrollDirection::Previous => self.previous(), + } + } + + fn next(&mut self) { if !self.items.is_empty() { self.state.select(Some( self.state.selected().map_or( @@ -190,7 +203,7 @@ impl StatefulList { } } - pub fn previous(&mut self) { + fn previous(&mut self) { if !self.items.is_empty() { self.state.select(Some( self.state @@ -600,6 +613,8 @@ impl LogsTz { pub struct Logs { lines: StatefulList>, tz: HashSet, + search_results: Vec, + search_term: Option, offset: u16, max_log_len: usize, adjusted_max_width: usize, @@ -614,6 +629,8 @@ impl Default for Logs { lines, tz: HashSet::new(), offset: 0, + search_term: None, + search_results: vec![], adjusted_max_width: 0, adjust_max_width_text_len: 0, max_log_len: 0, @@ -621,16 +638,185 @@ impl Default for Logs { } } +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub enum LogsButton { + Both, + Next, + Previous, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)] +pub struct LogSearch { + pub term: Option, + pub result: Option, + pub buttons: Option, +} + +/// LogSearch is used in FrameData +impl From<&Logs> for LogSearch { + fn from(l: &Logs) -> Self { + let buttons = l.lines.state.selected().as_ref().and_then(|x| { + let show_next = l.search_results.iter().any(|n| n > x); + let show_previous = l.search_results.iter().any(|n| n < x); + match (show_next, show_previous) { + (true, true) => Some(LogsButton::Both), + (true, false) => Some(LogsButton::Next), + (false, true) => Some(LogsButton::Previous), + (false, false) => None, + } + }); + Self { + term: l.search_term.clone(), + result: l.get_search_result(), + buttons, + } + } +} + impl Logs { - /// Only allow a new log line to be inserted if the log timestamp isn't in the tz HashSet - pub fn insert(&mut self, line: Text<'static>, tz: LogsTz) { - if self.tz.insert(tz) { - self.max_log_len = self.max_log_len.max(line.width()); - self.lines.items.push(line); + pub fn gen_log_search(&self) -> LogSearch { + LogSearch::from(self) + } + + /// Scroll to the next or previous search result, accounts for when currently selected line isn't in the results vec + pub fn search_scroll(&mut self, sd: &ScrollDirection) -> Option<()> { + if let Some(current_selected) = self.lines.state.selected() { + if let Some(current_position) = self + .search_results + .iter() + .position(|i| i == ¤t_selected) + { + if let Some(new_index) = match sd { + ScrollDirection::Next => current_position.checked_add(1), + ScrollDirection::Previous => current_position.checked_sub(1), + } { + if let Some(f) = self.search_results.get(new_index) { + self.lines.state.select(Some(*f)); + return Some(()); + } + } + } else { + let range = match sd { + ScrollDirection::Previous => (0..=current_selected).rev().collect::>(), + ScrollDirection::Next => (current_selected + ..=self + .search_results + .last() + .map_or_else(|| current_selected, |i| *i)) + .collect::>(), + }; + for i in range { + if self.search_results.contains(&i) { + self.lines.state.select(Some(i)); + return Some(()); + } + } + } + } + None + } + + /// Get a string x/y, where y is total matches found, and x is current ordered selected line + /// WIll be padded by max chars of total matches + fn get_search_result(&self) -> Option { + if self.search_results.is_empty() { + return None; + } + Some(self.lines.state.selected().map_or_else( + || format!("{}", self.search_results.len()), + |current_index| { + self.search_results + .iter() + .position(|i| i == ¤t_index) + .map_or_else( + || format!("{}", self.search_results.len()), + |index| { + let len = format!("{}", self.search_results.len()); + let len_width = len.chars().count(); + format!("{:>len_width$}/{len:>len_width$}", index + 1) + }, + ) + }, + )) + } + + /// Search through the logs for a matching string + pub fn search(&mut self, case_sensitive: bool, scroll: bool) { + if let Some(search_term) = self.search_term.as_ref() { + let term = if case_sensitive { + search_term.to_owned() + } else { + search_term.to_lowercase() + }; + self.search_results = self + .lines + .items + .iter() + .enumerate() + .filter_map(|(index, a)| { + a.lines + .iter() + .any(|b| { + b.spans.iter().any(|c| { + if case_sensitive { + c.content.contains(&term) + } else { + c.content.to_lowercase().contains(&term) + } + }) + }) + .then_some(index) + }) + .collect(); + if !self.search_results.is_empty() && scroll { + self.lines.state.select(self.search_results.last().copied()); + self.offset = 0; + } + } else { + self.search_results.clear(); } } - /// If scrolling horiztonally along the logs, display a counter of the position in the in the scroll, `x/y` + /// Set a single char into the filter term + pub fn search_term_push(&mut self, c: char, case_sensitive: bool) { + if let Some(term) = self.search_term.as_mut() { + term.push(c); + } else { + self.search_term = Some(format!("{c}")); + } + self.search(case_sensitive, true); + } + + /// Delete the final char of the filter term + pub fn search_term_pop(&mut self, case_sensitive: bool) { + if let Some(term) = self.search_term.as_mut() { + term.pop(); + if term.is_empty() { + self.search_term = None; + } + } + self.search(case_sensitive, true); + } + + /// Remove the filter completely + pub fn search_term_clear(&mut self) { + self.search_term = None; + self.search_results.clear(); + } + + /// Only allow a new log line to be inserted if the log timestamp isn't in the tz HashSet + pub fn insert(&mut self, line: Text<'static>, tz: LogsTz, case_sensitive: bool) { + if self.tz.insert(tz) { + self.max_log_len = self.max_log_len.max(line.width()); + self.lines.items.push(line); + // Maybe - Ideally we'd re-render here + if self.search_term.is_some() { + self.search(case_sensitive, false); + } + } + } + + /// If scrolling horizontally along the logs, display a counter of the position in the in the scroll, `x/y` pub fn get_scroll_title(&mut self, width: u16) -> Option { if self.horizontal_scroll_able(width) { let text_width = self.adjust_max_width_text_len; @@ -723,7 +909,7 @@ impl Logs { self.lines.get_state_title() } - /// Return true it currently selected cotnainer logs are wide enough to horizontally scroll + /// Return true it currently selected container logs are wide enough to horizontally scroll pub fn horizontal_scroll_able(&mut self, width: u16) -> bool { if self.lines.items.is_empty() { return false; @@ -748,21 +934,27 @@ impl Logs { self.offset = self.offset.saturating_sub(1); } + /// Scroll lines down by one pub fn next(&mut self) { self.lines.next(); } + /// Scroll lines up by one pub fn previous(&mut self) { self.lines.previous(); } + /// Go to the end of the lines pub fn end(&mut self) { self.lines.end(); } + + /// Go to the start of the lines pub fn start(&mut self) { self.lines.start(); } + /// Get total number of log lines pub const fn len(&self) -> usize { self.lines.items.len() } @@ -938,7 +1130,7 @@ mod tests { }; use crate::{ - app_data::{ContainerImage, Logs, LogsTz, RunningState}, + app_data::{ContainerImage, LogSearch, Logs, LogsTz, RunningState}, ui::log_sanitizer, }; @@ -1075,9 +1267,9 @@ mod tests { let mut logs = Logs::default(); let line = log_sanitizer::remove_ansi(input); - logs.insert(Text::from(line.clone()), tz.clone()); - logs.insert(Text::from(line.clone()), tz.clone()); - logs.insert(Text::from(line), tz); + logs.insert(Text::from(line.clone()), tz.clone(), true); + logs.insert(Text::from(line.clone()), tz.clone(), true); + logs.insert(Text::from(line), tz, true); assert_eq!(logs.lines.items.len(), 1); @@ -1085,9 +1277,9 @@ mod tests { let (tz, _) = LogsTz::splitter(input); let line = log_sanitizer::remove_ansi(input); - logs.insert(Text::from(line.clone()), tz.clone()); - logs.insert(Text::from(line.clone()), tz.clone()); - logs.insert(Text::from(line), tz); + logs.insert(Text::from(line.clone()), tz.clone(), true); + logs.insert(Text::from(line.clone()), tz.clone(), true); + logs.insert(Text::from(line), tz, true); assert_eq!(logs.lines.items.len(), 2); } @@ -1150,15 +1342,15 @@ mod tests { let input = "2023-01-14T19:13:30.783138328Z Hello world some long line".to_owned(); let (tz, _) = LogsTz::splitter(&input); - logs.insert(Text::from(input), tz); + logs.insert(Text::from(input), tz, true); let input = "2023-01-14T19:13:31.783138328Z Hello world some line".to_owned(); let (tz, _) = LogsTz::splitter(&input); - logs.insert(Text::from(input), tz); + logs.insert(Text::from(input), tz, true); let input = "2023-01-14T19:13:32.783138328Z Hello world".to_owned(); let (tz, _) = LogsTz::splitter(&input); - logs.insert(Text::from(input), tz); + logs.insert(Text::from(input), tz, true); logs.offset = 43; let result = logs.get_visible_logs( @@ -1188,14 +1380,14 @@ mod tests { let input = "short".to_owned(); let (tz, _) = LogsTz::splitter(&input); - logs.insert(Text::from(input), tz); + logs.insert(Text::from(input), tz, true); let result = logs.get_scroll_title(10); assert!(result.is_none()); let input = "2023-01-14T19:13:30.783138328Z Hello world some long line".to_owned(); let (tz, _) = LogsTz::splitter(&input); - logs.insert(Text::from(input), tz); + logs.insert(Text::from(input), tz, true); let result = logs.get_scroll_title(10); assert_eq!(result, Some(" 0/51 → ".to_owned())); @@ -1211,4 +1403,119 @@ mod tests { let result = logs.get_scroll_title(10); assert_eq!(result, Some(" ← 51/51 ".to_owned())); } + + #[test] + /// Test the log search + fn test_logsearch() { + let mut logs = Logs::default(); + + for i in 1..=10 { + let input = if i % 2 == 0 { + format!("{i}, hello world some long line {i}") + } else { + format!("{i}, Hello world some long line {i}") + }; + let (tz, _) = LogsTz::splitter(&input); + logs.insert(Text::from(input), tz, true); + } + + logs.search_term_push('H', true); + assert_eq!(logs.search_results, [0, 2, 4, 6, 8]); + logs.search_term_clear(); + logs.search_term_push('H', false); + assert_eq!(logs.search_results, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + } + + #[test] + /// Test the LogSearch::From() methods + fn test_logsearch_from() { + let mut logs = Logs::default(); + + for i in 1..=10 { + let input = format!("{i}, Hello world some long line {i}"); + let (tz, _) = LogsTz::splitter(&input); + logs.insert(Text::from(input), tz, true); + } + + let log_search = LogSearch::from(&logs); + assert_eq!( + log_search, + LogSearch { + term: None, + result: None, + buttons: None + } + ); + + logs.search_term_push('H', true); + let log_search = LogSearch::from(&logs); + assert_eq!( + log_search, + LogSearch { + term: Some("H".to_owned()), + result: Some("10/10".to_owned()), + buttons: Some(crate::app_data::LogsButton::Previous) + } + ); + + logs.previous(); + + let log_search = LogSearch::from(&logs); + assert_eq!( + log_search, + LogSearch { + term: Some("H".to_owned()), + result: Some(" 9/10".to_owned()), + buttons: Some(crate::app_data::LogsButton::Both) + } + ); + + logs.start(); + + let log_search = LogSearch::from(&logs); + assert_eq!( + log_search, + LogSearch { + term: Some("H".to_owned()), + result: Some(" 1/10".to_owned()), + buttons: Some(crate::app_data::LogsButton::Next) + } + ); + + logs.search_term_push('H', true); + let log_search = LogSearch::from(&logs); + assert_eq!( + log_search, + LogSearch { + term: Some("HH".to_owned()), + result: None, + buttons: None + } + ); + + logs.search_term_clear(); + logs.search_term_push('2', true); + let log_search = LogSearch::from(&logs); + assert_eq!(logs.lines.state.selected(), Some(1)); + assert_eq!( + log_search, + LogSearch { + term: Some("2".to_owned()), + result: Some("1/1".to_owned()), + buttons: None + } + ); + + logs.next(); + + let log_search = LogSearch::from(&logs); + assert_eq!( + log_search, + LogSearch { + term: Some("2".to_owned()), + result: Some("1".to_owned()), + buttons: Some(crate::app_data::LogsButton::Previous) + } + ); + } } diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index ab7993e..1139ad6 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -171,6 +171,19 @@ impl AppData { (self.filter.by, self.filter.term.as_ref()) } + pub fn log_search_scroll(&mut self, np: &ScrollDirection) { + if let Some(i) = self.get_mut_selected_container() { + if i.logs.search_scroll(np).is_some() { + self.rerender.update_draw(); + } + } + } + + pub fn gen_log_search(&self) -> Option { + self.get_selected_container() + .map(|i| i.logs.gen_log_search()) + } + /// Check if a given container can be inserted into the "visible" list, based on current filter term and filter_by fn can_insert(&self, container: &ContainerItem) -> bool { self.filter.term.as_ref().is_none_or(|term| { @@ -224,6 +237,31 @@ impl AppData { } } + pub fn logs_search_clear(&mut self) { + if let Some(selected_container) = self.get_mut_selected_container() { + selected_container.logs.search_term_clear(); + self.rerender.update_draw(); + } + } + + /// Set a single char into the filter term + pub fn log_search_push(&mut self, c: char) { + let cs = self.config.log_search_case_sensitive; + if let Some(selected_container) = self.get_mut_selected_container() { + selected_container.logs.search_term_push(c, cs); + self.rerender.update_draw(); + } + } + + /// Delete the final char of the filter term + pub fn log_search_pop(&mut self) { + let cs = self.config.log_search_case_sensitive; + if let Some(selected_container) = self.get_mut_selected_container() { + selected_container.logs.search_term_pop(cs); + self.rerender.update_draw(); + } + } + /// Re-filter the containers, used after the filter.by has been changed fn re_filter(&mut self) { self.containers.items.append(&mut self.hidden_containers); @@ -448,15 +486,8 @@ impl AppData { self.rerender.update_draw(); } - /// Select the next container - pub fn containers_next(&mut self) { - self.containers.next(); - self.rerender.update_draw(); - } - - /// select the previous container - pub fn containers_previous(&mut self) { - self.containers.previous(); + pub fn containers_scroll(&mut self, scroll: &ScrollDirection) { + self.containers.scroll(scroll); self.rerender.update_draw(); } @@ -576,17 +607,10 @@ impl AppData { } /// Change selected choice of docker commands of selected container - pub fn docker_controls_next(&mut self) { + pub fn docker_controls_scroll(&mut self, scroll: &ScrollDirection) { if let Some(i) = self.get_mut_selected_container() { - i.docker_controls.next(); - self.rerender.update_draw(); - } - } - - /// Change selected choice of docker commands of selected container - pub fn docker_controls_previous(&mut self) { - if let Some(i) = self.get_mut_selected_container() { - i.docker_controls.previous(); + i.docker_controls.scroll(scroll); + // i.docker_controls.next(); self.rerender.update_draw(); } } @@ -637,40 +661,36 @@ impl AppData { }) } - /// If scrolling horiztonally along the logs, display a counter of the position in the in the scroll, `x/y` + /// If scrolling horizontally along the logs, display a counter of the position in the in the scroll, `x/y` pub fn get_scroll_title(&mut self, width: u16) -> Option { self.get_mut_selected_container() .and_then(|i| i.logs.get_scroll_title(width)) } - /// Increase the logs offset, basically moving an invisible cursor back - pub fn log_back(&mut self) { - if let Some(i) = self.get_mut_selected_container() { - i.logs.back(); - self.rerender.update_draw(); - } - } - - /// Increase the logs offset, basically moving an invisible cursor forward - pub fn log_forward(&mut self, width: u16) { - if let Some(i) = self.get_mut_selected_container() { - i.logs.forward(width); - self.rerender.update_draw(); + pub fn logs_horizontal_scroll(&mut self, sd: &ScrollDirection, width: u16) { + match sd { + ScrollDirection::Next => { + if let Some(i) = self.get_mut_selected_container() { + i.logs.forward(width); + self.rerender.update_draw(); + } + } + ScrollDirection::Previous => { + if let Some(i) = self.get_mut_selected_container() { + i.logs.back(); + self.rerender.update_draw(); + } + } } } /// select next selected log line - pub fn log_next(&mut self) { + pub fn log_scroll(&mut self, scroll: &ScrollDirection) { if let Some(i) = self.get_mut_selected_container() { - i.logs.next(); - self.rerender.update_draw(); - } - } - - /// select previous selected log line - pub fn log_previous(&mut self) { - if let Some(i) = self.get_mut_selected_container() { - i.logs.previous(); + match scroll { + ScrollDirection::Next => i.logs.next(), + ScrollDirection::Previous => i.logs.previous(), + } self.rerender.update_draw(); } } @@ -857,7 +877,7 @@ impl AppData { // If removed container is currently selected, then change selected to previous // This will default to 0 in any edge cases if self.containers.state.selected().is_some() { - self.containers.previous(); + self.containers.scroll(&ScrollDirection::Previous); } // Check is some, else can cause out of bounds error, if containers get removed before a docker update if self.containers.items.get(index).is_some() { @@ -959,6 +979,8 @@ impl AppData { let format = self.config.timestamp_format.clone(); let config_tz = self.config.timezone.clone(); + let cs = self.config.log_search_case_sensitive; + let show_timestamp = self.config.show_timestamp; if let Some(container) = self.get_any_container_by_id(id) { @@ -985,7 +1007,7 @@ impl AppData { } else { log_sanitizer::remove_ansi(&i) }; - container.logs.insert(Text::from(lines), log_tz); + container.logs.insert(Text::from(lines), log_tz, cs); } // Set the logs selected row for each container @@ -1422,7 +1444,7 @@ mod tests { ); // Calling previous when at start has no effect - app_data.containers_previous(); + app_data.containers_scroll(&ScrollDirection::Previous); let result = app_data.get_selected_container_id(); assert_eq!(result, Some(ContainerId::from("1"))); let result = app_data.get_selected_container_id_state_name(); @@ -1445,7 +1467,7 @@ mod tests { // Advance list state by 1 app_data.containers_start(); - app_data.containers_next(); + app_data.containers.scroll(&ScrollDirection::Next); let result = app_data.get_container_state(); assert_eq!(result.selected(), Some(1)); @@ -1489,7 +1511,7 @@ mod tests { ); // Calling previous when at end has no effect - app_data.containers_next(); + app_data.containers.scroll(&ScrollDirection::Next); let result = app_data.get_selected_container_id(); assert_eq!(result, Some(ContainerId::from("3"))); let result = app_data.get_selected_container_id_state_name(); @@ -1510,7 +1532,7 @@ mod tests { let mut app_data = gen_appdata(&containers); app_data.containers_end(); - app_data.containers_previous(); + app_data.containers.scroll(&ScrollDirection::Previous); let result = app_data.get_container_state(); assert_eq!(result.selected(), Some(1)); assert_eq!(result.offset(), 0); @@ -1526,7 +1548,7 @@ mod tests { assert_eq!(result, None); app_data.containers.start(); - app_data.containers.next(); + app_data.containers.scroll(&ScrollDirection::Next); let result = app_data.get_selected_container(); assert_eq!(result, Some(&containers[1])); @@ -1613,7 +1635,7 @@ mod tests { let mut app_data = gen_appdata(&containers); app_data.containers_start(); app_data.docker_controls_start(); - app_data.docker_controls_next(); + app_data.docker_controls_scroll(&ScrollDirection::Next); let result = app_data.selected_docker_controls(); assert_eq!(result, Some(DockerCommand::Restart)); @@ -1631,7 +1653,7 @@ mod tests { assert_eq!(result, Some(DockerCommand::Delete)); // Next has no effect when at end - app_data.docker_controls_next(); + app_data.docker_controls_scroll(&ScrollDirection::Next); let result = app_data.selected_docker_controls(); assert_eq!(result, Some(DockerCommand::Delete)); } @@ -1643,14 +1665,14 @@ mod tests { let mut app_data = gen_appdata(&containers); app_data.containers_start(); app_data.docker_controls_end(); - app_data.docker_controls_previous(); + app_data.docker_controls_scroll(&ScrollDirection::Previous); let result = app_data.selected_docker_controls(); assert_eq!(result, Some(DockerCommand::Stop)); // previous has no effect when at start app_data.docker_controls_start(); - app_data.docker_controls_previous(); + app_data.docker_controls_scroll(&ScrollDirection::Previous); let result = app_data.selected_docker_controls(); assert_eq!(result, Some(DockerCommand::Pause)); } @@ -1914,7 +1936,7 @@ mod tests { assert_eq!(result, " 3/3 - container_1 - image_1"); // Change log state to no longer be at the end - app_data.log_previous(); + app_data.log_scroll(&ScrollDirection::Previous); let result = app_data.get_log_title(); assert_eq!(result, " 2/3 - container_1 - image_1"); } @@ -1935,7 +1957,7 @@ mod tests { assert_eq!(result, " - container_1 - image_1"); // change container - app_data.containers_next(); + app_data.containers_scroll(&ScrollDirection::Next); let result = app_data.get_log_title(); assert_eq!(result, " - container_2 - image_2"); @@ -1946,7 +1968,7 @@ mod tests { assert_eq!(result, " 3/3 - container_2 - image_2"); // Change log state to no longer be at the end - app_data.log_previous(); + app_data.log_scroll(&ScrollDirection::Previous); let result = app_data.get_log_title(); assert_eq!(result, " 2/3 - container_2 - image_2"); } @@ -2053,8 +2075,7 @@ mod tests { let result = app_data.get_log_title(); assert_eq!(result, " 1/3 - container_1 - image_1"); - app_data.log_next(); - + app_data.log_scroll(&ScrollDirection::Next); let result = app_data.get_log_state(); assert!(result.is_some()); assert_eq!(result.as_ref().unwrap().selected(), Some(1)); @@ -2063,7 +2084,7 @@ mod tests { let result = app_data.get_log_title(); assert_eq!(result, " 2/3 - container_1 - image_1"); - app_data.log_next(); + app_data.log_scroll(&ScrollDirection::Next); let result = app_data.get_log_state(); assert!(result.is_some()); assert_eq!(result.as_ref().unwrap().selected(), Some(2)); @@ -2071,7 +2092,7 @@ mod tests { let result = app_data.get_log_title(); assert_eq!(result, " 3/3 - container_1 - image_1"); - app_data.log_next(); + app_data.log_scroll(&ScrollDirection::Next); let result = app_data.get_log_state(); assert!(result.is_some()); @@ -2102,7 +2123,7 @@ mod tests { let result = app_data.get_log_title(); assert_eq!(result, " 3/3 - container_1 - image_1"); - app_data.log_previous(); + app_data.log_scroll(&ScrollDirection::Previous); let result = app_data.get_log_state(); assert!(result.is_some()); @@ -2111,7 +2132,7 @@ mod tests { let result = app_data.get_log_title(); assert_eq!(result, " 2/3 - container_1 - image_1"); - app_data.log_previous(); + app_data.log_scroll(&ScrollDirection::Previous); let result = app_data.get_log_state(); assert!(result.is_some()); assert_eq!(result.as_ref().unwrap().selected(), Some(0)); @@ -2119,7 +2140,7 @@ mod tests { let result = app_data.get_log_title(); assert_eq!(result, " 1/3 - container_1 - image_1"); - app_data.log_previous(); + app_data.log_scroll(&ScrollDirection::Previous); let result = app_data.get_log_state(); assert!(result.is_some()); assert_eq!(result.as_ref().unwrap().selected(), Some(0)); @@ -2413,7 +2434,7 @@ mod tests { } for _ in 0..=500 { - app_data.log_next(); + app_data.log_scroll(&ScrollDirection::Next); } let result = app_data.get_logs( Size { diff --git a/src/config/color_parser.rs b/src/config/color_parser.rs index 1599637..87dacd7 100644 --- a/src/config/color_parser.rs +++ b/src/config/color_parser.rs @@ -89,6 +89,24 @@ impl From> for AppColors { Self::map_color(fc.text.as_deref(), &mut app_colors.filter.text); } + // Log search + if let Some(ls) = config_colors.log_search { + Self::map_color( + ls.background.as_deref(), + &mut app_colors.log_search.background, + ); + Self::map_color( + ls.highlight.as_deref(), + &mut app_colors.log_search.highlight, + ); + + Self::map_color( + ls.button_text.as_deref(), + &mut app_colors.log_search.button_text, + ); + Self::map_color(ls.text.as_deref(), &mut app_colors.log_search.text); + } + // Help Popup if let Some(hp) = config_colors.popup_help { Self::map_color( @@ -238,6 +256,7 @@ optional_config_struct!( ConfigContainers, background, icon, text, text_rx, text_tx; ConfigContainerState, background, dead, exited, paused, removing, restarting, running_healthy, running_unhealthy, unknown; ConfigFilter, background, text, selected_filter_background, selected_filter_text, highlight; + ConfigLogSearch, background, text, button_text, highlight; ConfigHeadersBar, background, loading_spinner, text, text_selected; ConfigLogs, background, text ); @@ -251,6 +270,7 @@ config_struct!( Containers, background, icon, text, text_rx, text_tx; ContainerState, dead, exited, paused, removing, restarting, running_healthy, running_unhealthy, unknown; Filter, background, text, selected_filter_background, selected_filter_text, highlight; + LogSearch, background, text, button_text, highlight; HeadersBar, background, text_selected, loading_spinner, text; Logs, background, text; PopupDelete, background, text, text_highlight; @@ -269,6 +289,7 @@ pub struct ConfigColors { container_state: Option, containers: Option, filter: Option, + log_search: Option, headers_bar: Option, logs: Option, popup_delete: Option, @@ -397,6 +418,18 @@ impl Filter { } } +/// Default colours for the log search +impl LogSearch { + const fn new() -> Self { + Self { + background: Color::Reset, + highlight: Color::Magenta, + button_text: Color::Black, + text: Color::Gray, + } + } +} + /// Default colours for the logs panel, only applied if color_logs is false impl Logs { const fn new() -> Self { @@ -458,6 +491,7 @@ pub struct AppColors { pub commands: Commands, pub container_state: ContainerState, pub containers: Containers, + pub log_search: LogSearch, pub filter: Filter, pub headers_bar: HeadersBar, pub logs: Logs, @@ -477,6 +511,7 @@ impl AppColors { commands: Commands::new(), container_state: ContainerState::new(), containers: Containers::new(), + log_search: LogSearch::new(), filter: Filter::new(), headers_bar: HeadersBar::new(), logs: Logs::new(), diff --git a/src/config/config.toml b/src/config/config.toml index 182e7c5..8d5f1db 100644 --- a/src/config/config.toml +++ b/src/config/config.toml @@ -43,6 +43,9 @@ use_cli = false # Show the logs section - this can be changed during operation with the log_section_toggle key show_logs = true +# Use case-sensitive matching for logs +log_search_case_sensitive = true + ################# # Custom Keymap # ################# @@ -72,29 +75,25 @@ delete_confirm = ["y"] exec = ["e"] # Enter filter mode filter_mode = ["/", "F1"] + +# Enter log search mode +log_search_mode = ["#"] + # Quit at anytime quit = ["q"] # Save logs of selected container to file on disk save_logs = ["s"] -# TODO "scroll_down_many" will be removed in the next release -# scroll down a list by many -scroll_down_many = ["pagedown"] -# TODO rename in next release # scroll down a list by one item -scroll_down_one = ["down", "j"] +scroll_down = ["down", "j"] # scroll down to the end of a list scroll_end = ["end"] -# Modifier to scroll by 10 lines isntead of one, used in conjunction with scroll_up_x/scroll_down_x +# Modifier to scroll by 10 lines instead of one, used in conjunction with scroll_up/scroll_down scroll_many = ["control"] # scroll up to the start of a list scroll_start = ["home"] -# TODO "scroll_up_many" will be removed in the next release -# scroll up a list by many -scroll_up_many = ["pageup"] -# TODO rename in next release # scroll up a list by one item -scroll_up_one = ["up", "k"] +scroll_up = ["up", "k"] # Horizontal scroll of the logs log_scroll_forward = ["right"] log_scroll_back = ["left"] @@ -123,6 +122,9 @@ log_section_height_decrease = ["-"] log_section_height_increase = ["+"] # Toggle visibility of the log section log_section_toggle = ["\\"] + + + # Force a complete clear & redraw of the screen force_redraw = ["f"] @@ -200,6 +202,17 @@ selected_filter_text = "black" highlight = "magenta" +# The log search panel +[colors.log_search] +# Background color of panel +background = "reset" +# color of text +text = "gray" +# text color of the buttons text +button_text = "black" +# Highlighted text color +highlight = "magenta" + # The color the of Docker commands available for each container [colors.commands] # Background color of panel diff --git a/src/config/keymap_parser.rs b/src/config/keymap_parser.rs index 3f62e44..f75ff16 100644 --- a/src/config/keymap_parser.rs +++ b/src/config/keymap_parser.rs @@ -37,38 +37,33 @@ macro_rules! config_struct { optional_config_struct!( ConfigKeymap, clear, - force_redraw, - delete_deny, delete_confirm, + delete_deny, exec, filter_mode, - log_section_height_increase, - log_section_height_decrease, - log_section_toggle, - log_scroll_forward, + force_redraw, log_scroll_back, + log_scroll_forward, + log_search_mode, + log_section_height_decrease, + log_section_height_increase, + log_section_toggle, quit, save_logs, - // TODO remove in next release - scroll_down_many, - // TODO rename in next release - scroll_down_one, + scroll_down, scroll_end, scroll_start, - // TODO remove in next release - scroll_up_many, - // TODO rename in next release - scroll_up_one, + scroll_up, select_next_panel, select_previous_panel, - sort_by_name, - sort_by_state, - sort_by_status, sort_by_cpu, - sort_by_memory, sort_by_id, sort_by_image, + sort_by_memory, + sort_by_name, sort_by_rx, + sort_by_state, + sort_by_status, sort_by_tx, sort_reset, toggle_help, @@ -78,38 +73,33 @@ optional_config_struct!( config_struct!( Keymap, clear, - delete_deny, delete_confirm, + delete_deny, exec, filter_mode, force_redraw, - log_section_height_increase, - log_section_height_decrease, - log_section_toggle, - log_scroll_forward, log_scroll_back, + log_scroll_forward, + log_search_mode, + log_section_height_decrease, + log_section_height_increase, + log_section_toggle, quit, save_logs, - // TODO remove in next release - scroll_down_many, - // TODO rename in next release - scroll_down_one, + scroll_down, scroll_end, scroll_start, - // TODO remove in next release - scroll_up_many, - // TODO rename in next release - scroll_up_one, + scroll_up, select_next_panel, select_previous_panel, - sort_by_name, - sort_by_state, - sort_by_status, sort_by_cpu, - sort_by_memory, sort_by_id, sort_by_image, + sort_by_memory, + sort_by_name, sort_by_rx, + sort_by_state, + sort_by_status, sort_by_tx, sort_reset, toggle_help, @@ -125,24 +115,19 @@ impl Keymap { exec: (KeyCode::Char('e'), None), filter_mode: (KeyCode::Char('/'), Some(KeyCode::F(1))), force_redraw: (KeyCode::Char('f'), None), + log_scroll_back: (KeyCode::Left, None), + log_scroll_forward: (KeyCode::Right, None), + log_search_mode: (KeyCode::Char('#'), None), log_section_height_decrease: (KeyCode::Char('-'), None), log_section_height_increase: (KeyCode::Char('='), None), log_section_toggle: (KeyCode::Char('\\'), None), - log_scroll_back: (KeyCode::Left, None), - log_scroll_forward: (KeyCode::Right, None), quit: (KeyCode::Char('q'), None), save_logs: (KeyCode::Char('s'), None), - // TODO remove in next release - scroll_down_many: (KeyCode::PageDown, None), - // TODO rename in next release - scroll_down_one: (KeyCode::Down, Some(KeyCode::Char('j'))), + scroll_down: (KeyCode::Down, Some(KeyCode::Char('j'))), scroll_end: (KeyCode::End, None), - scroll_start: (KeyCode::Home, None), scroll_many: KeyModifiers::CONTROL, - // TODO remove in next release - scroll_up_many: (KeyCode::PageUp, None), - // TODO rename in next release - scroll_up_one: (KeyCode::Up, Some(KeyCode::Char('k'))), + scroll_start: (KeyCode::Home, None), + scroll_up: (KeyCode::Up, Some(KeyCode::Char('k'))), select_next_panel: (KeyCode::Tab, None), select_previous_panel: (KeyCode::BackTab, None), sort_by_cpu: (KeyCode::Char('4'), None), @@ -216,16 +201,11 @@ impl From> for Keymap { update_keymap(ck.force_redraw, &mut keymap.force_redraw, &mut clash); update_keymap(ck.quit, &mut keymap.quit, &mut clash); update_keymap(ck.save_logs, &mut keymap.save_logs, &mut clash); - update_keymap( - ck.scroll_down_many, - &mut keymap.scroll_down_many, - &mut clash, - ); - update_keymap(ck.scroll_down_one, &mut keymap.scroll_down_one, &mut clash); + update_keymap(ck.scroll_down, &mut keymap.scroll_down, &mut clash); update_keymap(ck.scroll_end, &mut keymap.scroll_end, &mut clash); update_keymap(ck.scroll_start, &mut keymap.scroll_start, &mut clash); - update_keymap(ck.scroll_up_many, &mut keymap.scroll_up_many, &mut clash); - update_keymap(ck.scroll_up_one, &mut keymap.scroll_up_one, &mut clash); + update_keymap(ck.scroll_up, &mut keymap.scroll_up, &mut clash); + update_keymap(ck.log_search_mode, &mut keymap.log_search_mode, &mut clash); update_keymap( ck.log_scroll_forward, &mut keymap.log_scroll_forward, @@ -416,19 +396,18 @@ mod tests { filter_mode: None, force_redraw: None, log_scroll_back: None, + log_search_mode: None, log_scroll_forward: None, log_section_height_decrease: None, log_section_height_increase: None, log_section_toggle: None, quit: None, save_logs: None, - scroll_down_many: None, - scroll_down_one: None, + scroll_down: None, scroll_end: None, scroll_start: None, scroll_many: None, - scroll_up_many: None, - scroll_up_one: None, + scroll_up: None, select_next_panel: None, select_previous_panel: None, sort_by_cpu: None, @@ -458,24 +437,23 @@ mod tests { let input = ConfigKeymap { clear: gen_v(("a", "b")), delete_confirm: gen_v(("c", "d")), - delete_deny: gen_v(("e", "fd")), + delete_deny: gen_v(("e", "f")), exec: gen_v(("g", "h")), filter_mode: gen_v(("i", "j")), force_redraw: gen_v(("k", "l")), + log_scroll_back: gen_v(("s", "t")), + log_scroll_forward: gen_v(("q", "r")), + log_search_mode: gen_v(("1", "2")), log_section_height_decrease: gen_v(("m", "n")), log_section_height_increase: gen_v(("o", "p")), - log_scroll_forward: gen_v(("q", "r")), - log_scroll_back: gen_v(("s", "t")), log_section_toggle: gen_v(("u", "v")), quit: gen_v(("w", "x")), save_logs: gen_v(("y", "z")), - scroll_down_many: gen_v(("1", "2")), - scroll_down_one: gen_v(("3", "4")), + scroll_down: gen_v(("3", "4")), scroll_end: gen_v(("5", "6")), scroll_many: Some(vec!["alt".to_owned()]), scroll_start: gen_v(("7", "8")), - scroll_up_many: gen_v(("9", "0")), - scroll_up_one: gen_v(("F1", "F2")), + scroll_up: gen_v(("F1", "F2")), select_next_panel: gen_v(("F3", "F4")), select_previous_panel: gen_v(("F5", "F6")), sort_by_cpu: gen_v(("F7", "F8")), @@ -496,39 +474,38 @@ mod tests { let expected = Keymap { clear: (KeyCode::Char('a'), Some(KeyCode::Char('b'))), - delete_deny: (KeyCode::Char('e'), None), delete_confirm: (KeyCode::Char('c'), Some(KeyCode::Char('d'))), + delete_deny: (KeyCode::Char('e'), Some(KeyCode::Char('f'))), exec: (KeyCode::Char('g'), Some(KeyCode::Char('h'))), filter_mode: (KeyCode::Char('i'), Some(KeyCode::Char('j'))), force_redraw: (KeyCode::Char('k'), Some(KeyCode::Char('l'))), - log_section_height_increase: (KeyCode::Char('o'), Some(KeyCode::Char('p'))), - log_section_height_decrease: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), - log_section_toggle: (KeyCode::Char('u'), Some(KeyCode::Char('v'))), - log_scroll_forward: (KeyCode::Char('q'), Some(KeyCode::Char('r'))), log_scroll_back: (KeyCode::Char('s'), Some(KeyCode::Char('t'))), + log_scroll_forward: (KeyCode::Char('q'), Some(KeyCode::Char('r'))), + log_search_mode: (KeyCode::Char('1'), Some(KeyCode::Char('2'))), + log_section_height_decrease: (KeyCode::Char('m'), Some(KeyCode::Char('n'))), + log_section_height_increase: (KeyCode::Char('o'), Some(KeyCode::Char('p'))), + log_section_toggle: (KeyCode::Char('u'), Some(KeyCode::Char('v'))), quit: (KeyCode::Char('w'), Some(KeyCode::Char('x'))), save_logs: (KeyCode::Char('y'), Some(KeyCode::Char('z'))), - scroll_down_many: (KeyCode::Char('1'), Some(KeyCode::Char('2'))), - scroll_down_one: (KeyCode::Char('3'), Some(KeyCode::Char('4'))), + scroll_down: (KeyCode::Char('3'), Some(KeyCode::Char('4'))), scroll_end: (KeyCode::Char('5'), Some(KeyCode::Char('6'))), + scroll_many: KeyModifiers::ALT, scroll_start: (KeyCode::Char('7'), Some(KeyCode::Char('8'))), - scroll_up_many: (KeyCode::Char('9'), Some(KeyCode::Char('0'))), - scroll_up_one: (KeyCode::F(1), Some(KeyCode::F(2))), + scroll_up: (KeyCode::F(1), Some(KeyCode::F(2))), select_next_panel: (KeyCode::F(3), Some(KeyCode::F(4))), select_previous_panel: (KeyCode::F(5), Some(KeyCode::F(6))), - sort_by_name: (KeyCode::Up, Some(KeyCode::Down)), - sort_by_state: (KeyCode::Char('['), Some(KeyCode::Char(']'))), - sort_by_status: (KeyCode::Tab, None), sort_by_cpu: (KeyCode::F(7), Some(KeyCode::F(8))), - sort_by_memory: (KeyCode::Home, Some(KeyCode::End)), sort_by_id: (KeyCode::F(9), Some(KeyCode::F(10))), sort_by_image: (KeyCode::F(11), Some(KeyCode::F(12))), + sort_by_memory: (KeyCode::Home, Some(KeyCode::End)), + sort_by_name: (KeyCode::Up, Some(KeyCode::Down)), sort_by_rx: (KeyCode::Left, Some(KeyCode::Right)), + sort_by_state: (KeyCode::Char('['), Some(KeyCode::Char(']'))), + sort_by_status: (KeyCode::Tab, None), sort_by_tx: (KeyCode::PageDown, Some(KeyCode::PageUp)), sort_reset: (KeyCode::Char(','), Some(KeyCode::Char('.'))), toggle_help: (KeyCode::Char('-'), Some(KeyCode::Char('='))), toggle_mouse_capture: (KeyCode::Char('\\'), Some(KeyCode::Char('/'))), - scroll_many: KeyModifiers::ALT, }; assert_eq!(expected, result); } diff --git a/src/config/mod.rs b/src/config/mod.rs index 24e6877..699768e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -13,6 +13,9 @@ pub use {color_parser::AppColors, keymap_parser::Keymap}; mod parse_args; mod parse_config_file; +// TODO use a global pub static oncelock for the config +// static CELL: OnceLock = OnceLock::new(); + #[derive(Debug, Clone)] #[allow(clippy::struct_excessive_bools)] pub struct Config { @@ -23,14 +26,15 @@ pub struct Config { pub host: Option, pub in_container: bool, pub keymap: Keymap, + pub log_search_case_sensitive: bool, pub raw_logs: bool, pub save_dir: Option, + pub show_logs: bool, pub show_self: bool, pub show_std_err: bool, pub show_timestamp: bool, - pub timezone: Option, pub timestamp_format: String, - pub show_logs: bool, + pub timezone: Option, pub use_cli: bool, } @@ -44,15 +48,16 @@ impl From<&Args> for Config { host: args.host.clone(), in_container: Self::check_if_in_container(), keymap: Keymap::new(), + log_search_case_sensitive: true, raw_logs: args.raw, save_dir: Self::try_get_logs_dir(args.save_dir.as_ref()), + show_logs: true, show_self: !args.show_self, show_std_err: !args.no_std_err, show_timestamp: !args.timestamp, - timezone: Self::parse_timezone(args.timezone.clone()), timestamp_format: Self::parse_timestamp_format(None), + timezone: Self::parse_timezone(args.timezone.clone()), use_cli: args.use_cli, - show_logs: true, } } } @@ -67,15 +72,16 @@ impl From for Config { host: config_file.host, in_container: Self::check_if_in_container(), keymap: Keymap::from(config_file.keymap), + log_search_case_sensitive: config_file.log_search_case_sensitive.unwrap_or(true), raw_logs: config_file.raw_logs.unwrap_or(false), save_dir: Self::try_get_logs_dir(config_file.save_dir.as_ref()), + show_logs: config_file.show_logs.unwrap_or(true), show_self: config_file.show_self.unwrap_or(false), show_std_err: config_file.show_std_err.unwrap_or(true), show_timestamp: config_file.show_timestamp.unwrap_or(true), - timezone: Self::parse_timezone(config_file.timezone), timestamp_format: Self::parse_timestamp_format(config_file.timestamp_format), + timezone: Self::parse_timezone(config_file.timezone), use_cli: config_file.use_cli.unwrap_or(false), - show_logs: config_file.show_logs.unwrap_or(true), } } } diff --git a/src/config/parse_config_file.rs b/src/config/parse_config_file.rs index ead111a..d61991a 100644 --- a/src/config/parse_config_file.rs +++ b/src/config/parse_config_file.rs @@ -67,15 +67,16 @@ pub struct ConfigFile { pub gui: Option, pub host: Option, pub keymap: Option, + pub log_search_case_sensitive: Option, pub raw_logs: Option, pub save_dir: Option, + pub show_logs: Option, pub show_self: Option, pub show_std_err: Option, pub show_timestamp: Option, pub timestamp_format: Option, pub timezone: Option, pub use_cli: Option, - pub show_logs: Option, } impl ConfigFile { diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index fcf098c..000577b 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -19,7 +19,7 @@ use uuid::Uuid; mod message; use crate::{ - app_data::{AppData, DockerCommand, Header}, + app_data::{AppData, DockerCommand, Header, ScrollDirection}, app_error::AppError, config, docker_data::DockerMessage, @@ -74,10 +74,12 @@ impl InputHandler { if contains(Status::DeleteConfirm) { self.button_intersect(mouse_event).await; } else if !contains(Status::Error) - | !contains(Status::Help) - | !contains(Status::DeleteConfirm) - | !contains(Status::Filter) + && !contains(Status::Help) + && !contains(Status::DeleteConfirm) + && !contains(Status::Filter) + && !contains(Status::SearchLogs) { + // TODO handle state where you want to scroll log search results with the mouse wheel self.mouse_press(mouse_event, modifider); } } @@ -294,23 +296,12 @@ impl InputHandler { } } - /// Advance the "cursor" along the logs - fn logs_forward(&self, modifier: KeyModifiers) { + fn logs_horizontal_scroll(&self, modifier: KeyModifiers, sd: &ScrollDirection) { let panel = self.gui_state.lock().get_selected_panel(); if panel == SelectablePanel::Logs { for _ in 0..self.get_modifier_total(modifier) { let width = self.gui_state.lock().get_screen_width(); - self.app_data.lock().log_forward(width); - } - } - } - - /// Retreat the "cursor" along the logs - fn logs_back(&self, modifier: KeyModifiers) { - let panel = self.gui_state.lock().get_selected_panel(); - if panel == SelectablePanel::Logs { - for _ in 0..self.get_modifier_total(modifier) { - self.app_data.lock().log_back(); + self.app_data.lock().logs_horizontal_scroll(sd, width); } } } @@ -388,6 +379,66 @@ impl InputHandler { } } + /// Actions to take when Filter status active + fn handle_search_logs(&self, key_code: KeyCode, modifier: KeyModifiers) { + match key_code { + KeyCode::Esc => { + self.app_data.lock().logs_search_clear(); + self.gui_state.lock().status_del(Status::SearchLogs); + } + _ if KeyCode::Enter == key_code + || self.keymap.log_search_mode.0 == key_code + || self.keymap.log_search_mode.1 == Some(key_code) => + { + self.gui_state.lock().status_del(Status::SearchLogs); + } + + _ if self.keymap.log_scroll_back.0 == key_code + || self.keymap.log_scroll_back.1 == Some(key_code) => + { + self.logs_horizontal_scroll(modifier, &ScrollDirection::Previous); + } + + _ if self.keymap.log_scroll_forward.0 == key_code + || self.keymap.log_scroll_forward.1 == Some(key_code) => + { + self.logs_horizontal_scroll(modifier, &ScrollDirection::Next); + } + + _ if self.keymap.scroll_down.0 == key_code => { + self.app_data + .lock() + .log_search_scroll(&ScrollDirection::Next); + // TODO should only do this is log_search_scroll returns some + // Need to wait til app_data and gui_data is combined + self.gui_state + .lock() + .set_logs_panel_selected(&self.app_data); + // + } + + _ if self.keymap.scroll_up.0 == key_code => { + self.app_data + .lock() + .log_search_scroll(&ScrollDirection::Previous); + // TODO should only do this is log_search_scroll returns some + // Need to wait til app_data and gui_data is combined + self.gui_state + .lock() + .set_logs_panel_selected(&self.app_data); + } + + // handle up and down keys + KeyCode::Backspace => { + self.app_data.lock().log_search_pop(); + } + KeyCode::Char(x) => { + self.app_data.lock().log_search_push(x); + } + _ => (), + } + } + /// Actions to take when Filter status active fn handle_filter(&self, key_code: KeyCode) { match key_code { @@ -569,32 +620,16 @@ impl InputHandler { self.scroll_end_key(); } - _ if self.keymap.scroll_up_one.0 == key_code - || self.keymap.scroll_up_one.1 == Some(key_code) => + _ if self.keymap.scroll_up.0 == key_code + || self.keymap.scroll_up.1 == Some(key_code) => { - self.scroll_up(modifier); + self.scroll(modifier, &ScrollDirection::Previous); } - _ if self.keymap.scroll_up_many.0 == key_code - || self.keymap.scroll_up_many.1 == Some(key_code) => + _ if self.keymap.scroll_down.0 == key_code + || self.keymap.scroll_down.1 == Some(key_code) => { - for _ in 0..=6 { - self.scroll_up(modifier); - } - } - - _ if self.keymap.scroll_down_one.0 == key_code - || self.keymap.scroll_down_one.1 == Some(key_code) => - { - self.scroll_down(modifier); - } - - _ if self.keymap.scroll_down_many.0 == key_code - || self.keymap.scroll_down_many.1 == Some(key_code) => - { - for _ in 0..=6 { - self.scroll_down(modifier); - } + self.scroll(modifier, &ScrollDirection::Next); } _ if self.keymap.filter_mode.0 == key_code @@ -604,16 +639,27 @@ impl InputHandler { self.docker_tx.send(DockerMessage::Update).await.ok(); } + _ if self.keymap.log_search_mode.0 == key_code + || self.keymap.log_search_mode.1 == Some(key_code) => + { + if !self.gui_state.lock().get_show_logs() { + self.gui_state.lock().toggle_show_logs(); + } + self.gui_state.lock().status_push(Status::SearchLogs); + } + _ if self.keymap.log_scroll_back.0 == key_code || self.keymap.log_scroll_back.1 == Some(key_code) => { - self.logs_back(modifier); + self.logs_horizontal_scroll(modifier, &ScrollDirection::Previous); + // self.logs_back(modifier); } _ if self.keymap.log_scroll_forward.0 == key_code || self.keymap.log_scroll_forward.1 == Some(key_code) => { - self.logs_forward(modifier); + self.logs_horizontal_scroll(modifier, &ScrollDirection::Next); + // self.logs_forward(modifier); } KeyCode::Enter => self.enter_key().await, @@ -631,13 +677,14 @@ impl InputHandler { let contains_exec = contains(Status::Exec); let contains_filter = contains(Status::Filter); let contains_delete = contains(Status::DeleteConfirm); + let contains_search_logs = contains(Status::SearchLogs); if !contains_exec { let is_q = || key_code == self.keymap.quit.0 || Some(key_code) == self.keymap.quit.1; if key_modifier == KeyModifiers::CONTROL && key_code == KeyCode::Char('c') - || is_q() && !contains_filter + || is_q() && !contains_filter && !contains_search_logs { - // Always just quit on Ctrl + c/C or q/Q, unless in Filter status active + // Always just quit on Ctrl + c/C or q/Q, unless in filter/search_logs mode, i.e. when user inmput can include the q key self.quit(); } @@ -647,6 +694,8 @@ impl InputHandler { self.handle_help(key_code); } else if contains_filter { self.handle_filter(key_code); + } else if contains_search_logs { + self.handle_search_logs(key_code, key_modifier); } else if contains_delete { self.handle_delete(key_code).await; } else { @@ -685,8 +734,8 @@ impl InputHandler { } } else { match mouse_event.kind { - MouseEventKind::ScrollUp => self.scroll_up(modifier), - MouseEventKind::ScrollDown => self.scroll_down(modifier), + MouseEventKind::ScrollUp => self.scroll(modifier, &ScrollDirection::Previous), + MouseEventKind::ScrollDown => self.scroll(modifier, &ScrollDirection::Next), MouseEventKind::Down(MouseButton::Left) => { let mouse_point = Rect::new(mouse_event.column, mouse_event.row, 1, 1); let header = self.gui_state.lock().get_intersect_header(mouse_point); @@ -706,38 +755,25 @@ impl InputHandler { } /// Change state to next, depending which panel is currently in focus - fn scroll_down(&self, modifier: KeyModifiers) { - let selected_panel = self.gui_state.lock().get_selected_panel(); - match selected_panel { - SelectablePanel::Containers => { - for _ in 0..self.get_modifier_total(modifier) { - self.app_data.lock().containers_next(); + fn scroll(&self, modifier: KeyModifiers, scroll: &ScrollDirection) { + let status = self.gui_state.lock().get_status(); + if status.contains(&Status::SearchLogs) { + self.app_data.lock().log_search_scroll(scroll); + } else { + let selected_panel = self.gui_state.lock().get_selected_panel(); + match selected_panel { + SelectablePanel::Containers => { + for _ in 0..self.get_modifier_total(modifier) { + self.app_data.lock().containers_scroll(scroll); + } } - } - SelectablePanel::Logs => { - for _ in 0..self.get_modifier_total(modifier) { - self.app_data.lock().log_next(); + SelectablePanel::Logs => { + for _ in 0..self.get_modifier_total(modifier) { + self.app_data.lock().log_scroll(scroll); + } } + SelectablePanel::Commands => self.app_data.lock().docker_controls_scroll(scroll), } - SelectablePanel::Commands => self.app_data.lock().docker_controls_next(), - } - } - - /// Change state to previous, depending which panel is currently in focus - fn scroll_up(&self, modifier: KeyModifiers) { - let selected_panel = self.gui_state.lock().get_selected_panel(); - match selected_panel { - SelectablePanel::Containers => { - for _ in 0..self.get_modifier_total(modifier) { - self.app_data.lock().containers_previous(); - } - } - SelectablePanel::Logs => { - for _ in 0..self.get_modifier_total(modifier) { - self.app_data.lock().log_previous(); - } - } - SelectablePanel::Commands => self.app_data.lock().docker_controls_previous(), } } } diff --git a/src/main.rs b/src/main.rs index 4de8728..30ea787 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ #![allow(clippy::collapsible_if)] +// #![allow(unused)] // Zigbuild is stuck on 1.87.0, which means Mac builds won't work when using collapsible ifs use app_data::AppData; @@ -175,6 +176,7 @@ mod tests { show_std_err: false, in_container: false, save_dir: None, + log_search_case_sensitive: true, raw_logs: false, show_self: false, app_colors: AppColors::new(), diff --git a/src/ui/draw_blocks/commands.rs b/src/ui/draw_blocks/commands.rs index 805ae0f..d01af86 100644 --- a/src/ui/draw_blocks/commands.rs +++ b/src/ui/draw_blocks/commands.rs @@ -59,6 +59,7 @@ mod tests { use ratatui::style::{Color, Modifier}; use crate::{ + app_data::ScrollDirection, config::AppColors, tests::gen_container_summary, ui::{ @@ -169,7 +170,10 @@ mod tests { .app_data .lock() .update_containers(vec![gen_container_summary(1, "paused")]); - setup.app_data.lock().docker_controls_next(); + setup + .app_data + .lock() + .docker_controls_scroll(&ScrollDirection::Next); setup .terminal @@ -363,7 +367,10 @@ mod tests { .app_data .lock() .update_containers(vec![gen_container_summary(1, "paused")]); - setup.app_data.lock().docker_controls_next(); + setup + .app_data + .lock() + .docker_controls_scroll(&ScrollDirection::Next); setup .terminal diff --git a/src/ui/draw_blocks/filter.rs b/src/ui/draw_blocks/filter.rs index 987a46e..101ce2d 100644 --- a/src/ui/draw_blocks/filter.rs +++ b/src/ui/draw_blocks/filter.rs @@ -52,7 +52,7 @@ pub fn draw(area: Rect, colors: AppColors, frame: &mut Frame, fd: &FrameData) { line.extend_from_slice(&filter_by_spans(colors, fd)); line.extend_from_slice(&[ Span::styled( - " term: ", + " filter term: ", Style::default() .fg(colors.filter.highlight) .add_modifier(Modifier::BOLD), @@ -116,7 +116,7 @@ mod tests { assert_eq!(result_cell.bg, Color::Gray); assert_eq!(result_cell.fg, Color::Black); } - 47..=53 => { + 47..=60 => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Magenta); assert_eq!(result_cell.modifier, Modifier::BOLD); @@ -166,7 +166,7 @@ mod tests { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::Black); } - 5..=11 | 27..=46 | 54..=55 => { + 5..=11 | 27..=46 | 61..=62 => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Gray); } @@ -174,7 +174,7 @@ mod tests { assert_eq!(result_cell.bg, Color::Gray); assert_eq!(result_cell.fg, Color::Black); } - 47..=53 => { + 47..=60 => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Magenta); assert_eq!(result_cell.modifier, Modifier::BOLD); @@ -222,7 +222,7 @@ mod tests { assert_eq!(result_cell.bg, Color::Gray); assert_eq!(result_cell.fg, Color::Black); } - 47..=53 => { + 47..=60 => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Magenta); assert_eq!(result_cell.modifier, Modifier::BOLD); @@ -273,7 +273,7 @@ mod tests { assert_eq!(result_cell.bg, Color::Blue); assert_eq!(result_cell.fg, Color::Yellow); } - 5..=11 | 27..=46 | 54..=55 => { + 5..=11 | 27..=46 | 61..=62 => { assert_eq!(result_cell.bg, Color::White); assert_eq!(result_cell.fg, Color::Magenta); } @@ -281,7 +281,7 @@ mod tests { assert_eq!(result_cell.bg, Color::Red); assert_eq!(result_cell.fg, Color::Yellow); } - 47..=53 => { + 47..=60 => { assert_eq!(result_cell.bg, Color::White); assert_eq!(result_cell.fg, Color::Blue); assert_eq!(result_cell.modifier, Modifier::BOLD); diff --git a/src/ui/draw_blocks/help.rs b/src/ui/draw_blocks/help.rs index 83a3051..7e16abc 100644 --- a/src/ui/draw_blocks/help.rs +++ b/src/ui/draw_blocks/help.rs @@ -106,8 +106,6 @@ impl HelpInfo { or(), button_item("j k"), or(), - button_item("PgUp PgDown"), - or(), button_item("Home End"), button_desc("scroll vertically"), ]), @@ -119,7 +117,7 @@ impl HelpInfo { Line::from(vec![ space(), button_item("ctrl"), - button_desc("increase scroll speed, used in conjuction scroll keys"), + button_desc("increase scroll speed, used in conjunction scroll keys"), ]), Line::from(vec![ space(), @@ -162,6 +160,11 @@ impl HelpInfo { button_item("/"), button_desc("enter filter mode"), ]), + Line::from(vec![ + space(), + button_item("#"), + button_desc("enter log search mode"), + ]), Line::from(vec![space(), button_item("0"), button_desc("stop sort")]), Line::from(vec![ space(), @@ -277,10 +280,8 @@ impl HelpInfo { let descriptions = [ or_secondary(km.select_next_panel, "select next panel"), or_secondary(km.select_previous_panel, "select previous panel"), - or_secondary(km.scroll_down_one, "scroll list down by one"), - or_secondary(km.scroll_up_one, "scroll list up by one"), - or_secondary(km.scroll_down_many, "scroll list down by many"), - or_secondary(km.scroll_up_many, "scroll list by up many"), + or_secondary(km.scroll_down, "scroll list down by one"), + or_secondary(km.scroll_up, "scroll list up by one"), or_secondary(km.scroll_end, "scroll list to end"), or_secondary(km.scroll_start, "scroll list to start"), or_secondary(km.log_scroll_forward, "horizontal scroll logs right"), @@ -288,7 +289,7 @@ impl HelpInfo { Line::from(vec![ space(), button_item(km.scroll_many.to_string().as_str()), - button_desc("increase scroll speed, used in conjuction scroll keys"), + button_desc("increase scroll speed, used in conjunction scroll keys"), ]), Line::from(vec![ space(), @@ -310,6 +311,7 @@ impl HelpInfo { "toggle mouse capture - if disabled, text on screen can be selected & copied", ), or_secondary(km.filter_mode, "enter filter mode"), + or_secondary(km.log_search_mode, "enter log search mode"), or_secondary(km.sort_reset, "reset container sorting"), or_secondary(km.sort_by_name, "sort containers by name"), or_secondary(km.sort_by_state, "sort containers by state"), @@ -462,7 +464,7 @@ mod tests { /// This test is incredibly annoying /// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg); fn test_draw_blocks_help() { - let mut setup = test_setup(87, 37, true, true); + let mut setup = test_setup(87, 39, true, true); let tz = setup.app_data.lock().config.timezone.clone(); setup @@ -482,45 +484,32 @@ mod tests { for (row_index, result_row) in get_result(&setup) { for (result_cell_index, result_cell) in result_row.iter().enumerate() { - println!( - "{} {} {} {} {}", - row_index, - result_cell_index, - result_cell.symbol(), - result_cell.bg, - result_cell.fg - ); match (row_index, result_cell_index) { // first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area - (0 | 36, _) | (0..=35, 0 | 86) => { + (0 | 38, _) | (0..=37, 0 | 86) => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Reset); } - // border is red on black - (1 | 34, _) | (1..=31, 1 | 85) => { - assert_eq!(result_cell.bg, Color::Magenta); - assert_eq!(result_cell.fg, Color::Black); - } // Buttons - (2..=10, 2..=85) + (2..=10, 2..=84) | (12, 19..=66) | (14, 2..=10 | 13..=27) - | (15, 2..=10 | 13..=21 | 24..=40 | 43..=56) - | (16 | 27 | 29, 2..=10) + | (15, 2..=10 | 13..=21 | 24..=37) + | (16 | 28 | 30, 2..=10) + | (19..=26 | 29 | 31, 2..=8) | (17, 2..=11) - | (18 | 26, 2..=12) - | (19 | 20 | 21 | 22 | 24 | 25 | 28 | 23 | 30, 2..=8) + | (18 | 27, 2..=12) | (24, 2..=9 | 12..=18) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::White); } - // The URL is yellow and underlined - (33, 25..=60) => { + // The URL is white on yellow and underlined + (34, 25..=60) => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::White); assert_eq!(result_cell.modifier, Modifier::UNDERLINED); } - // The rest is red on black + // The rest is black on magenta _ => { assert_eq!(result_cell.bg, Color::Magenta); assert_eq!(result_cell.fg, Color::Black); @@ -535,7 +524,7 @@ mod tests { /// This test is incredibly annoying /// println!("{} {} {} {} {}", row_index, result_cell_index, result_cell.symbol(), result_cell.bg, result_cell.fg); fn test_draw_blocks_help_custom_colors() { - let mut setup = test_setup(87, 37, true, true); + let mut setup = test_setup(87, 39, true, true); let mut colors = AppColors::new(); let tz = setup.app_data.lock().config.timezone.clone(); @@ -561,35 +550,30 @@ mod tests { for (result_cell_index, result_cell) in result_row.iter().enumerate() { match (row_index, result_cell_index) { // first & last row, and first & last char on each row, is reset/reset, making sure that the help info is centered in the given area - (0 | 36, _) | (0..=35, 0 | 86) => { + (0 | 38, _) | (0..=37, 0 | 86) => { assert_eq!(result_cell.bg, Color::Reset); assert_eq!(result_cell.fg, Color::Reset); } - // border is red on black - (1 | 34, _) | (1..=31, 1 | 85) => { - assert_eq!(result_cell.bg, Color::Black); - assert_eq!(result_cell.fg, Color::Red); - } // Buttons - (2..=10, 2..=85) + (2..=10, 2..=84) | (12, 19..=66) | (14, 2..=10 | 13..=27) - | (15, 2..=10 | 13..=21 | 24..=40 | 43..=56) - | (16 | 27 | 29, 2..=10) + | (15, 2..=10 | 13..=21 | 24..=37) + | (16 | 28 | 30, 2..=10) + | (19..=26 | 29 | 31, 2..=8) | (17, 2..=11) - | (18 | 26, 2..=12) - | (19 | 20 | 21 | 22 | 24 | 25 | 28 | 23 | 30, 2..=8) + | (18 | 27, 2..=12) | (24, 2..=9 | 12..=18) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Yellow); } - // The URL is yellow and underlined - (33, 25..=60) => { + // The URL is white on yellow and underlined + (34, 25..=60) => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Yellow); assert_eq!(result_cell.modifier, Modifier::UNDERLINED); } - // The rest is red on black + // The rest is black on magenta _ => { assert_eq!(result_cell.bg, Color::Black); assert_eq!(result_cell.fg, Color::Red); @@ -610,6 +594,7 @@ mod tests { delete_deny: (KeyCode::Char('c'), None), exec: (KeyCode::Char('d'), None), filter_mode: (KeyCode::Char('e'), None), + log_search_mode: (KeyCode::Char('7'), None), force_redraw: (KeyCode::Char('f'), None), log_scroll_back: (KeyCode::Char('g'), None), log_scroll_forward: (KeyCode::Char('h'), None), @@ -618,13 +603,11 @@ mod tests { log_section_toggle: (KeyCode::Char('k'), None), quit: (KeyCode::Char('l'), None), save_logs: (KeyCode::Char('m'), None), - scroll_down_many: (KeyCode::Char('n'), None), - scroll_down_one: (KeyCode::Char('o'), None), + scroll_down: (KeyCode::Char('o'), None), scroll_end: (KeyCode::Char('p'), None), scroll_many: KeyModifiers::ALT, scroll_start: (KeyCode::Char('q'), None), - scroll_up_many: (KeyCode::Char('r'), None), - scroll_up_one: (KeyCode::Char('s'), None), + scroll_up: (KeyCode::Char('s'), None), select_next_panel: (KeyCode::Char('t'), None), select_previous_panel: (KeyCode::Char('u'), None), sort_by_cpu: (KeyCode::Char('v'), None), @@ -662,6 +645,7 @@ mod tests { delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))), exec: (KeyCode::Char('d'), Some(KeyCode::Char('D'))), filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))), + log_search_mode: (KeyCode::Char('m'), Some(KeyCode::Char('M'))), force_redraw: (KeyCode::Char('f'), Some(KeyCode::Char('F'))), log_scroll_back: (KeyCode::Char('f'), Some(KeyCode::Char('F'))), log_scroll_forward: (KeyCode::Char('g'), Some(KeyCode::Char('G'))), @@ -670,13 +654,11 @@ mod tests { log_section_toggle: (KeyCode::Char('j'), Some(KeyCode::Char('J'))), quit: (KeyCode::Char('k'), Some(KeyCode::Char('K'))), save_logs: (KeyCode::Char('l'), Some(KeyCode::Char('L'))), - scroll_down_many: (KeyCode::Char('m'), Some(KeyCode::Char('M'))), - scroll_down_one: (KeyCode::Char('n'), Some(KeyCode::Char('N'))), + scroll_down: (KeyCode::Char('n'), Some(KeyCode::Char('N'))), scroll_end: (KeyCode::Char('o'), Some(KeyCode::Char('O'))), scroll_many: KeyModifiers::ALT, scroll_start: (KeyCode::Char('p'), Some(KeyCode::Char('P'))), - scroll_up_many: (KeyCode::Char('q'), Some(KeyCode::Char('Q'))), - scroll_up_one: (KeyCode::Char('r'), Some(KeyCode::Char('R'))), + scroll_up: (KeyCode::Char('r'), Some(KeyCode::Char('R'))), select_next_panel: (KeyCode::Char('s'), Some(KeyCode::Char('S'))), select_previous_panel: (KeyCode::Char('t'), Some(KeyCode::Char('T'))), sort_by_cpu: (KeyCode::Char('u'), Some(KeyCode::Char('U'))), @@ -705,6 +687,7 @@ mod tests { #[test] /// Help panel will show custom keymap if in use, with either one or two definition for each entry + #[allow(clippy::todo)] fn test_draw_blocks_help_one_and_two_definitions() { let mut setup = test_setup(110, 50, true, true); @@ -714,6 +697,7 @@ mod tests { delete_deny: (KeyCode::Char('c'), Some(KeyCode::Char('C'))), exec: (KeyCode::Char('d'), None), filter_mode: (KeyCode::Char('e'), Some(KeyCode::Char('E'))), + log_search_mode: (KeyCode::Char('8'), None), force_redraw: (KeyCode::Char('f'), None), log_scroll_back: (KeyCode::Char('g'), Some(KeyCode::Char('G'))), log_scroll_forward: (KeyCode::Char('h'), None), @@ -722,13 +706,11 @@ mod tests { log_section_toggle: (KeyCode::Char('k'), Some(KeyCode::Char('K'))), quit: (KeyCode::Char('l'), None), save_logs: (KeyCode::Char('m'), Some(KeyCode::Char('M'))), - scroll_down_many: (KeyCode::Char('n'), None), - scroll_down_one: (KeyCode::Char('o'), Some(KeyCode::Char('O'))), + scroll_down: (KeyCode::Char('o'), Some(KeyCode::Char('O'))), scroll_end: (KeyCode::Char('p'), None), scroll_many: KeyModifiers::ALT, scroll_start: (KeyCode::Char('q'), Some(KeyCode::Char('Q'))), - scroll_up_many: (KeyCode::Char('r'), None), - scroll_up_one: (KeyCode::Char('s'), Some(KeyCode::Char('S'))), + scroll_up: (KeyCode::Char('s'), Some(KeyCode::Char('S'))), select_next_panel: (KeyCode::Char('t'), None), select_previous_panel: (KeyCode::Char('u'), Some(KeyCode::Char('U'))), sort_by_cpu: (KeyCode::Char('v'), None), @@ -742,7 +724,7 @@ mod tests { sort_by_tx: (KeyCode::Char('3'), None), sort_reset: (KeyCode::Char('4'), Some(KeyCode::Char('5'))), toggle_help: (KeyCode::Char('5'), None), - toggle_mouse_capture: (KeyCode::Char('6'), Some(KeyCode::Char('7'))), + toggle_mouse_capture: (KeyCode::Char('6'), Some(KeyCode::Char('#'))), }; let tz = setup.app_data.lock().config.timezone.clone(); @@ -759,7 +741,7 @@ mod tests { #[test] fn test_draw_blocks_help_show_timezone() { - let mut setup = test_setup(87, 37, true, true); + let mut setup = test_setup(87, 39, true, true); setup .terminal diff --git a/src/ui/draw_blocks/logs.rs b/src/ui/draw_blocks/logs.rs index 61d501f..ebf6ec7 100644 --- a/src/ui/draw_blocks/logs.rs +++ b/src/ui/draw_blocks/logs.rs @@ -81,7 +81,7 @@ mod tests { use uuid::Uuid; use crate::{ - app_data::{ContainerImage, ContainerName}, + app_data::{ContainerImage, ContainerName, ScrollDirection}, config::AppColors, ui::{ FrameData, Status, @@ -309,7 +309,7 @@ mod tests { ); }) .unwrap(); - setup.app_data.lock().log_previous(); + setup.app_data.lock().log_scroll(&ScrollDirection::Previous); let fd = FrameData::from((&setup.app_data, &setup.gui_state)); setup diff --git a/src/ui/draw_blocks/mod.rs b/src/ui/draw_blocks/mod.rs index d65a37c..507a913 100644 --- a/src/ui/draw_blocks/mod.rs +++ b/src/ui/draw_blocks/mod.rs @@ -23,6 +23,7 @@ pub mod info; pub mod logs; pub mod popup; pub mod ports; +pub mod search_logs; pub const NAME_TEXT: &str = r#" 88 @@ -160,19 +161,11 @@ pub mod tests { fn from(data: (&Arc>, &Arc>)) -> Self { let (mut app_data, gui_data) = (data.0.lock(), data.1.lock()); - // let container_section_height = app_data.get_container_len(); - // let container_section_height = if container_section_height < 12 { - // u16::try_from(container_section_height + 5).unwrap_or_default() - // } else { - // 12 - // }; - let (filter_by, filter_term) = app_data.get_filter(); Self { chart_data: app_data.get_chart_data(), color_logs: app_data.config.color_logs, columns: app_data.get_width(), - // container_section_height, container_title: app_data.get_container_title(), delete_confirm: gui_data.get_delete_container(), filter_by, @@ -182,6 +175,7 @@ pub mod tests { show_logs: gui_data.get_show_logs(), info_text: gui_data.info_box_text.clone(), is_loading: gui_data.is_loading(), + log_search: app_data.gen_log_search(), loading_icon: gui_data.get_loading().to_string(), log_height: gui_data.get_log_height(), log_title: app_data.get_log_title(), @@ -230,7 +224,6 @@ pub mod tests { /// Just a shorthand for when enumerating over result cells pub fn get_result( setup: &'_ TuiTestSetup, - // w: u16, ) -> std::iter::Enumerate> { setup .terminal diff --git a/src/ui/draw_blocks/search_logs.rs b/src/ui/draw_blocks/search_logs.rs new file mode 100644 index 0000000..81b1e31 --- /dev/null +++ b/src/ui/draw_blocks/search_logs.rs @@ -0,0 +1,479 @@ +use crossterm::event::KeyCode; +use ratatui::{ + Frame, + layout::{Constraint, Direction, Layout, Rect}, + style::{Modifier, Style}, + text::{Line, Span}, + widgets::Paragraph, +}; + +use crate::{ + app_data::LogsButton, + config::{AppColors, Keymap}, + ui::FrameData, +}; + +// background, text, selected_text, highlight; +/// Draw the filter bar +pub fn draw(area: Rect, colors: AppColors, frame: &mut Frame, fd: &FrameData, keymap: &Keymap) { + let style_but = Style::default() + .fg(colors.log_search.button_text) + .bg(colors.log_search.highlight); + let style_desc = Style::default() + .fg(colors.log_search.text) + .bg(colors.log_search.background); + let space = || Span::from(" "); + + let mut line = vec![ + Span::styled(" Esc ", style_but), + Span::styled(" clear ", style_desc), + space(), + ]; + line.extend([Span::styled( + " search term: ", + Style::default() + .fg(colors.log_search.highlight) + .bg(colors.log_search.background) + .add_modifier(Modifier::BOLD), + )]); + + if let Some(log_search) = fd.log_search.as_ref() { + line.extend([ + Span::styled( + log_search + .term + .as_ref() + .map_or(String::new(), std::clone::Clone::clone), + Style::default() + .fg(colors.log_search.text) + .bg(colors.log_search.background), + ), + space(), + ]); + } + + let left_text = Paragraph::new(Line::from(line)) + .alignment(ratatui::layout::Alignment::Left) + .style(Style::default().bg(colors.log_search.background)); + let mut line = vec![]; + if let Some(log_search) = fd.log_search.as_ref() { + if let Some(buttons) = log_search.buttons.as_ref() { + let down = if keymap.scroll_down.0 == KeyCode::Down { + "↑".to_owned() + } else { + keymap.scroll_down.0.to_string() + }; + let up = if keymap.scroll_up.0 == KeyCode::Up { + "↓".to_owned() + } else { + keymap.scroll_up.0.to_string() + }; + let next = [ + space(), + Span::styled(format!(" {up} "), style_but), + Span::styled(" next ", style_desc), + ]; + let previous = [ + space(), + Span::styled(format!(" {down} "), style_but), + Span::styled(" previous ", style_desc), + ]; + + match buttons { + LogsButton::Both => line.extend(previous.into_iter().chain(next)), + LogsButton::Next => line.extend(next), + LogsButton::Previous => line.extend(previous), + } + } + + if let Some(results) = log_search.result.as_ref() { + line.extend([ + Span::styled( + " matches: ", + Style::default() + .fg(colors.log_search.highlight) + .bg(colors.log_search.background) + .add_modifier(Modifier::BOLD), + ), + Span::styled( + results, + Style::default() + .fg(colors.log_search.text) + .bg(colors.log_search.background), + ), + ]); + } + } + let right_text = Paragraph::new(Line::from(line)) + .alignment(ratatui::layout::Alignment::Right) + .style(Style::default().bg(colors.log_search.background)); + + let line_split = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(area); + frame.render_widget(left_text, line_split[0]); + frame.render_widget(right_text, line_split[1]); +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + + use crossterm::event::KeyCode; + use insta::assert_snapshot; + use ratatui::style::{Color, Modifier}; + + use crate::{ + config::{AppColors, Keymap}, + ui::{ + FrameData, + draw_blocks::tests::{get_result, insert_logs, test_setup}, + }, + }; + + #[test] + /// Filter row is drawn correctly & colors are correct + /// Colours change when filter_by option is changed + fn test_draw_blocks_log_search_row() { + let mut setup = test_setup(140, 1, true, true); + + setup + .gui_state + .lock() + .status_push(crate::ui::Status::SearchLogs); + setup + .terminal + .draw(|f| { + super::draw(setup.area, AppColors::new(), f, &setup.fd, &Keymap::new()); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + + for (_, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match result_cell_index { + 0..=4 => { + assert_eq!(result_cell.bg, Color::Magenta); + assert_eq!(result_cell.fg, Color::Black); + } + 5..=11 => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + 13..=26 => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Magenta); + assert_eq!(result_cell.modifier, Modifier::BOLD); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Reset); + } + } + } + } + } + + #[test] + /// Log item found, previous button visible + fn test_draw_blocks_log_search_match_previous() { + let mut setup = test_setup(140, 1, true, true); + + insert_logs(&setup); + setup + .gui_state + .lock() + .status_push(crate::ui::Status::SearchLogs); + + setup.app_data.lock().log_search_push('e'); + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + + setup + .terminal + .draw(|f| { + super::draw(setup.area, AppColors::new(), f, &fd, &Keymap::new()); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + + for (_, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match result_cell_index { + 0..=4 | 114..=116 => { + assert_eq!(result_cell.bg, Color::Magenta); + assert_eq!(result_cell.fg, Color::Black); + } + 5..=11 | 27 | 117..=126 | 137..=139 => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + 13..=26 | 127..=136 => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Magenta); + assert_eq!(result_cell.modifier, Modifier::BOLD); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Reset); + } + } + } + } + } + + #[test] + /// Log item found, next button visible + fn test_draw_blocks_log_search_match_next() { + let mut setup = test_setup(140, 1, true, true); + + insert_logs(&setup); + + setup + .gui_state + .lock() + .status_push(crate::ui::Status::SearchLogs); + + setup.app_data.lock().log_search_push('e'); + setup + .app_data + .lock() + .log_scroll(&crate::app_data::ScrollDirection::Previous); + setup + .app_data + .lock() + .log_scroll(&crate::app_data::ScrollDirection::Previous); + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + super::draw(setup.area, AppColors::new(), f, &fd, &Keymap::new()); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + + for (_, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match result_cell_index { + 0..=4 | 118..=120 => { + assert_eq!(result_cell.bg, Color::Magenta); + assert_eq!(result_cell.fg, Color::Black); + } + 5..=11 | 27 | 121..=126 | 137..=139 => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + 13..=26 | 127..=136 => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Magenta); + assert_eq!(result_cell.modifier, Modifier::BOLD); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Reset); + } + } + } + } + } + + #[test] + /// Log item found, next & previous button visible + fn test_draw_blocks_log_search_match_both_next_previous() { + let mut setup = test_setup(140, 1, true, true); + + insert_logs(&setup); + + setup + .gui_state + .lock() + .status_push(crate::ui::Status::SearchLogs); + + setup.app_data.lock().log_search_push('e'); + setup + .app_data + .lock() + .log_scroll(&crate::app_data::ScrollDirection::Previous); + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + super::draw(setup.area, AppColors::new(), f, &fd, &Keymap::new()); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + + for (_, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match result_cell_index { + 0..=4 | 104..=106 | 118..=120 => { + assert_eq!(result_cell.bg, Color::Magenta); + assert_eq!(result_cell.fg, Color::Black); + } + 5..=11 | 27 | 107..=116 | 121..=126 | 137..=139 => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + 13..=26 | 127..=136 => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Magenta); + assert_eq!(result_cell.modifier, Modifier::BOLD); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Reset); + } + } + } + } + } + + #[test] + /// No log item found + fn test_draw_blocks_log_search_match_none() { + let mut setup = test_setup(140, 1, true, true); + + insert_logs(&setup); + + setup + .gui_state + .lock() + .status_push(crate::ui::Status::SearchLogs); + + setup.app_data.lock().log_search_push('z'); + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + + setup + .terminal + .draw(|f| { + super::draw(setup.area, AppColors::new(), f, &fd, &Keymap::new()); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + + for (_, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match result_cell_index { + 0..=4 => { + assert_eq!(result_cell.bg, Color::Magenta); + assert_eq!(result_cell.fg, Color::Black); + } + 5..=11 | 27 => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Gray); + } + 13..=26 => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Magenta); + assert_eq!(result_cell.modifier, Modifier::BOLD); + } + _ => { + assert_eq!(result_cell.bg, Color::Reset); + assert_eq!(result_cell.fg, Color::Reset); + } + } + } + } + } + + #[test] + /// Custom keymap for scroll buttons + fn test_draw_blocks_log_search_keymap() { + let mut setup = test_setup(140, 1, true, true); + + insert_logs(&setup); + + let mut keymap = setup.app_data.lock().config.keymap.clone(); + keymap.scroll_up = (KeyCode::Char('a'), None); + keymap.scroll_down = (KeyCode::Char('b'), None); + + setup + .gui_state + .lock() + .status_push(crate::ui::Status::SearchLogs); + + setup.app_data.lock().log_search_push('e'); + setup + .app_data + .lock() + .log_scroll(&crate::app_data::ScrollDirection::Previous); + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + + setup + .terminal + .draw(|f| { + super::draw(setup.area, AppColors::new(), f, &fd, &keymap); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + } + + #[test] + /// Custom colours applied + fn test_draw_blocks_log_search_colors() { + let mut setup = test_setup(140, 1, true, true); + + insert_logs(&setup); + + setup + .gui_state + .lock() + .status_push(crate::ui::Status::SearchLogs); + + setup.app_data.lock().log_search_push('e'); + setup + .app_data + .lock() + .log_scroll(&crate::app_data::ScrollDirection::Previous); + + let mut colors = AppColors::new(); + + colors.log_search.background = Color::White; + colors.log_search.highlight = Color::Blue; + colors.log_search.button_text = Color::Yellow; + colors.log_search.text = Color::Magenta; + + let fd = FrameData::from((&setup.app_data, &setup.gui_state)); + setup + .terminal + .draw(|f| { + super::draw(setup.area, colors, f, &fd, &Keymap::new()); + }) + .unwrap(); + + assert_snapshot!(setup.terminal.backend()); + + for (_, result_row) in get_result(&setup) { + for (result_cell_index, result_cell) in result_row.iter().enumerate() { + match result_cell_index { + 0..=4 | 104..=106 | 118..=120 => { + assert_eq!(result_cell.bg, Color::Blue); + assert_eq!(result_cell.fg, Color::Yellow); + } + 5..=11 | 27 | 107..=116 | 121..=126 | 137..=139 => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Magenta); + } + 13..=26 | 127..=136 => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Blue); + assert_eq!(result_cell.modifier, Modifier::BOLD); + } + _ => { + assert_eq!(result_cell.bg, Color::White); + assert_eq!(result_cell.fg, Color::Reset); + } + } + } + } + } +} diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row.snap index 9f5b920..f7b9563 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row.snap @@ -2,4 +2,4 @@ source: src/ui/draw_blocks/filter.rs expression: setup.terminal.backend() --- -" Esc clear ← by → Name Image Status All term: " +" Esc clear ← by → Name Image Status All filter term: " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row_custom_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row_custom_colors.snap index eb2fd5d..fbf7f0f 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row_custom_colors.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row_custom_colors.snap @@ -2,4 +2,4 @@ source: src/ui/draw_blocks/filter.rs expression: setup.terminal.backend() --- -" Esc clear ← by → Name Image Status All term: cd " +" Esc clear ← by → Name Image Status All filter term: cd " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row_filter_by.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row_filter_by.snap index 9f5b920..f7b9563 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row_filter_by.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row_filter_by.snap @@ -2,4 +2,4 @@ source: src/ui/draw_blocks/filter.rs expression: setup.terminal.backend() --- -" Esc clear ← by → Name Image Status All term: " +" Esc clear ← by → Name Image Status All filter term: " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row_text.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row_text.snap index eb2fd5d..fbf7f0f 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row_text.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__filter__tests__draw_blocks_filter_row_text.snap @@ -2,4 +2,4 @@ source: src/ui/draw_blocks/filter.rs expression: setup.terminal.backend() --- -" Esc clear ← by → Name Image Status All term: cd " +" Esc clear ← by → Name Image Status All filter term: cd " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap index ad841ca..9d29eeb 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help.snap @@ -17,9 +17,9 @@ expression: setup.terminal.backend() " │ A simple tui to view & control docker containers │ " " │ │ " " │ ( tab ) or ( shift+tab ) change panels │ " -" │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) scroll vertically │ " +" │ ( ↑ ↓ ) or ( j k ) or ( Home End ) scroll vertically │ " " │ ( ← → ) horizontal scroll across logs │ " -" │ ( ctrl ) increase scroll speed, used in conjuction scroll keys │ " +" │ ( ctrl ) increase scroll speed, used in conjunction scroll keys │ " " │ ( enter ) send docker container command │ " " │ ( e ) exec into a container │ " " │ ( f ) force clear the screen & redraw the gui │ " @@ -27,6 +27,7 @@ expression: setup.terminal.backend() " │ ( s ) save logs to file │ " " │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " " │ ( F1 ) or ( / ) enter filter mode │ " +" │ ( # ) enter log search mode │ " " │ ( 0 ) stop sort │ " " │ ( 1 - 9 ) sort by header - or click header │ " " │ ( - = ) change log section height │ " @@ -37,5 +38,6 @@ expression: setup.terminal.backend() " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " " │ │ " +" │ │ " " ╰───────────────────────────────────────────────────────────────────────────────────╯ " " " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap index ad841ca..9d29eeb 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_colors.snap @@ -17,9 +17,9 @@ expression: setup.terminal.backend() " │ A simple tui to view & control docker containers │ " " │ │ " " │ ( tab ) or ( shift+tab ) change panels │ " -" │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) scroll vertically │ " +" │ ( ↑ ↓ ) or ( j k ) or ( Home End ) scroll vertically │ " " │ ( ← → ) horizontal scroll across logs │ " -" │ ( ctrl ) increase scroll speed, used in conjuction scroll keys │ " +" │ ( ctrl ) increase scroll speed, used in conjunction scroll keys │ " " │ ( enter ) send docker container command │ " " │ ( e ) exec into a container │ " " │ ( f ) force clear the screen & redraw the gui │ " @@ -27,6 +27,7 @@ expression: setup.terminal.backend() " │ ( s ) save logs to file │ " " │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " " │ ( F1 ) or ( / ) enter filter mode │ " +" │ ( # ) enter log search mode │ " " │ ( 0 ) stop sort │ " " │ ( 1 - 9 ) sort by header - or click header │ " " │ ( - = ) change log section height │ " @@ -37,5 +38,6 @@ expression: setup.terminal.backend() " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " " │ │ " +" │ │ " " ╰───────────────────────────────────────────────────────────────────────────────────╯ " " " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap index f8933a4..97b4d43 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_one_definition.snap @@ -19,13 +19,11 @@ expression: setup.terminal.backend() " │ ( u ) select previous panel │ " " │ ( o ) scroll list down by one │ " " │ ( s ) scroll list up by one │ " -" │ ( n ) scroll list down by many │ " -" │ ( r ) scroll list by up many │ " " │ ( p ) scroll list to end │ " " │ ( q ) scroll list to start │ " " │ ( h ) horizontal scroll logs right │ " " │ ( g ) horizontal scroll logs left │ " -" │ ( Alt ) increase scroll speed, used in conjuction scroll keys │ " +" │ ( Alt ) increase scroll speed, used in conjunction scroll keys │ " " │ ( enter ) send docker container command │ " " │ ( d ) exec into a container │ " " │ ( f ) force clear the screen & redraw the gui │ " @@ -33,6 +31,7 @@ expression: setup.terminal.backend() " │ ( m ) save logs to file │ " " │ ( 6 ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " " │ ( e ) enter filter mode │ " +" │ ( 7 ) enter log search mode │ " " │ ( 4 ) reset container sorting │ " " │ ( z ) sort containers by name │ " " │ ( 1 ) sort containers by state │ " @@ -51,4 +50,5 @@ expression: setup.terminal.backend() " │ │ " " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " +" │ │ " " ╰────────────────────────────────────────────────────────────────────────────────────╯ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap index 40866fb..f884948 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_custom_keymap_two_definitions.snap @@ -19,13 +19,11 @@ expression: setup.terminal.backend() " │ ( t ) or ( T ) select previous panel │ " " │ ( n ) or ( N ) scroll list down by one │ " " │ ( r ) or ( R ) scroll list up by one │ " -" │ ( m ) or ( M ) scroll list down by many │ " -" │ ( q ) or ( Q ) scroll list by up many │ " " │ ( o ) or ( O ) scroll list to end │ " " │ ( p ) or ( P ) scroll list to start │ " " │ ( g ) or ( G ) horizontal scroll logs right │ " " │ ( f ) or ( F ) horizontal scroll logs left │ " -" │ ( Alt ) increase scroll speed, used in conjuction scroll keys │ " +" │ ( Alt ) increase scroll speed, used in conjunction scroll keys │ " " │ ( enter ) send docker container command │ " " │ ( d ) or ( D ) exec into a container │ " " │ ( f ) or ( F ) force clear the screen & redraw the gui │ " @@ -33,6 +31,7 @@ expression: setup.terminal.backend() " │ ( l ) or ( L ) save logs to file │ " " │ ( 5 ) or ( Page Down ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " " │ ( e ) or ( E ) enter filter mode │ " +" │ ( m ) or ( M ) enter log search mode │ " " │ ( 3 ) or ( 6 ) reset container sorting │ " " │ ( y ) or ( Y ) sort containers by name │ " " │ ( 0 ) or ( 9 ) sort containers by state │ " @@ -51,4 +50,5 @@ expression: setup.terminal.backend() " │ │ " " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " +" │ │ " " ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap index 3694cac..6fcfd2e 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_one_and_two_definitions.snap @@ -19,20 +19,19 @@ expression: setup.terminal.backend() " │ ( u ) or ( U ) select previous panel │ " " │ ( o ) or ( O ) scroll list down by one │ " " │ ( s ) or ( S ) scroll list up by one │ " -" │ ( n ) scroll list down by many │ " -" │ ( r ) scroll list by up many │ " " │ ( p ) scroll list to end │ " " │ ( q ) or ( Q ) scroll list to start │ " " │ ( h ) horizontal scroll logs right │ " " │ ( g ) or ( G ) horizontal scroll logs left │ " -" │ ( Alt ) increase scroll speed, used in conjuction scroll keys │ " +" │ ( Alt ) increase scroll speed, used in conjunction scroll keys │ " " │ ( enter ) send docker container command │ " " │ ( d ) exec into a container │ " " │ ( f ) force clear the screen & redraw the gui │ " " │ ( 5 ) toggle this help information - or click heading │ " " │ ( m ) or ( M ) save logs to file │ " -" │ ( 6 ) or ( 7 ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " +" │ ( 6 ) or ( # ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " " │ ( e ) or ( E ) enter filter mode │ " +" │ ( 8 ) enter log search mode │ " " │ ( 4 ) or ( 5 ) reset container sorting │ " " │ ( z ) sort containers by name │ " " │ ( 1 ) sort containers by state │ " @@ -51,4 +50,5 @@ expression: setup.terminal.backend() " │ │ " " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " +" │ │ " " ╰────────────────────────────────────────────────────────────────────────────────────────────╯ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap index 67b6242..6c8b9ef 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__help__tests__draw_blocks_help_show_timezone.snap @@ -18,9 +18,9 @@ expression: setup.terminal.backend() " │ logs timezone: Asia/Tokyo │ " " │ │ " " │ ( tab ) or ( shift+tab ) change panels │ " -" │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) scroll vertically │ " +" │ ( ↑ ↓ ) or ( j k ) or ( Home End ) scroll vertically │ " " │ ( ← → ) horizontal scroll across logs │ " -" │ ( ctrl ) increase scroll speed, used in conjuction scroll keys │ " +" │ ( ctrl ) increase scroll speed, used in conjunction scroll keys │ " " │ ( enter ) send docker container command │ " " │ ( e ) exec into a container │ " " │ ( f ) force clear the screen & redraw the gui │ " @@ -28,6 +28,7 @@ expression: setup.terminal.backend() " │ ( s ) save logs to file │ " " │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ " " │ ( F1 ) or ( / ) enter filter mode │ " +" │ ( # ) enter log search mode │ " " │ ( 0 ) stop sort │ " " │ ( 1 - 9 ) sort by header - or click header │ " " │ ( - = ) change log section height │ " @@ -38,4 +39,5 @@ expression: setup.terminal.backend() " │ currently an early work in progress, all and any input appreciated │ " " │ https://github.com/mrjackwills/oxker │ " " │ │ " +" │ │ " " ╰───────────────────────────────────────────────────────────────────────────────────╯ " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_colors.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_colors.snap new file mode 100644 index 0000000..aa27273 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_colors.snap @@ -0,0 +1,5 @@ +--- +source: src/ui/draw_blocks/search_logs.rs +expression: setup.terminal.backend() +--- +" Esc clear search term: e ↑ previous ↓ next matches: 2/3" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_keymap.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_keymap.snap new file mode 100644 index 0000000..eafd76b --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_keymap.snap @@ -0,0 +1,5 @@ +--- +source: src/ui/draw_blocks/search_logs.rs +expression: setup.terminal.backend() +--- +" Esc clear search term: e b previous a next matches: 2/3" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_match_both_next_previous.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_match_both_next_previous.snap new file mode 100644 index 0000000..aa27273 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_match_both_next_previous.snap @@ -0,0 +1,5 @@ +--- +source: src/ui/draw_blocks/search_logs.rs +expression: setup.terminal.backend() +--- +" Esc clear search term: e ↑ previous ↓ next matches: 2/3" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_match_next.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_match_next.snap new file mode 100644 index 0000000..c341ef3 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_match_next.snap @@ -0,0 +1,5 @@ +--- +source: src/ui/draw_blocks/search_logs.rs +expression: setup.terminal.backend() +--- +" Esc clear search term: e ↓ next matches: 1/3" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_match_none.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_match_none.snap new file mode 100644 index 0000000..4b4d112 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_match_none.snap @@ -0,0 +1,5 @@ +--- +source: src/ui/draw_blocks/search_logs.rs +expression: setup.terminal.backend() +--- +" Esc clear search term: z " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_match_previous.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_match_previous.snap new file mode 100644 index 0000000..52a2e16 --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_match_previous.snap @@ -0,0 +1,5 @@ +--- +source: src/ui/draw_blocks/search_logs.rs +expression: setup.terminal.backend() +--- +" Esc clear search term: e ↑ previous matches: 3/3" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_row.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_row.snap new file mode 100644 index 0000000..27d8aff --- /dev/null +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__search_logs__tests__draw_blocks_log_search_row.snap @@ -0,0 +1,5 @@ +--- +source: src/ui/draw_blocks/search_logs.rs +expression: setup.terminal.backend() +--- +" Esc clear search term: " diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap index f6603f1..a69c2d8 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_help_panel.snap @@ -18,9 +18,9 @@ expression: setup.terminal.backend() "│ │ A simple tui to view & control docker containers │ │" "│ │ │ │" "│ │ ( tab ) or ( shift+tab ) change panels │ │" -"│ │ ( ↑ ↓ ) or ( j k ) or ( PgUp PgDown ) or ( Home End ) scroll vertically │ │" +"│ │ ( ↑ ↓ ) or ( j k ) or ( Home End ) scroll vertically │ │" "│ │ ( ← → ) horizontal scroll across logs │ │" -"│ │ ( ctrl ) increase scroll speed, used in conjuction scroll keys │ │" +"│ │ ( ctrl ) increase scroll speed, used in conjunction scroll keys │ │" "│ │ ( enter ) send docker container command │ │" "│ │ ( e ) exec into a container │ │" "│ │ ( f ) force clear the screen & redraw the gui │ │" @@ -28,16 +28,16 @@ expression: setup.terminal.backend() "│ │ ( s ) save logs to file │ │" "│ │ ( m ) toggle mouse capture - if disabled, text on screen can be selected & copied │ │" "│ │ ( F1 ) or ( / ) enter filter mode │ │" +"│ │ ( # ) enter log search mode │ │" "│ │ ( 0 ) stop sort │ │" "│ │ ( 1 - 9 ) sort by header - or click header │ │" -"│ │ ( - = ) change log section height │ │" -"╰────────────────────────────────────│ ( \ ) toggle log section visibility │────────────────────────────────────╯" -"╭───────────────────────── cpu 03.00%│ ( esc ) close dialog │──────╮╭────────── ports ───────────╮" -"│10.00%│ •• │ ( q ) quit at any time │ ││ ip private public│" -"│ │ • • │ │ ││ 8001 │" -"│ │ •• • │ currently an early work in progress, all and any input appreciated │ ││127.0.0.1 8003 8003│" -"│ │ • • │ https://github.com/mrjackwills/oxker │ ││ │" -"│ │ •• • • │ │ ││ │" +"╰────────────────────────────────────│ ( - = ) change log section height │────────────────────────────────────╯" +"╭───────────────────────── cpu 03.00%│ ( \ ) toggle log section visibility │──────╮╭────────── ports ───────────╮" +"│10.00%│ •• │ ( esc ) close dialog │ ││ ip private public│" +"│ │ • • │ ( q ) quit at any time │ ││ 8001 │" +"│ │ •• • │ │ ││127.0.0.1 8003 8003│" +"│ │ • • │ currently an early work in progress, all and any input appreciated │ ││ │" +"│ │ •• • • │ https://github.com/mrjackwills/oxker │ ││ │" "│ │• •• │ │ ││ │" "│ │• • ╰────────────────────────────────────────────────────────────────────────────────────╯ ││ │" "│ │ ││ │ ││ │" diff --git a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_with_filter_bar.snap b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_with_filter_bar.snap index 4e98c14..4a576be 100644 --- a/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_with_filter_bar.snap +++ b/src/ui/draw_blocks/snapshots/oxker__ui__draw_blocks__tests__draw_blocks_whole_layout_with_filter_bar.snap @@ -31,4 +31,4 @@ expression: setup.terminal.backend() "│ │• •• ││ │• •• ││ │" "│ │ ││ │ ││ │" "╰───────────────────────────────────────────────────────────────╯╰───────────────────────────────────────────────────────────────╯╰────────────────────────────╯" -" Esc clear ← by → Name Image Status All term: r_1 " +" Esc clear ← by → Name Image Status All filter term: r_1 " diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs index dd0aac9..9b598c4 100644 --- a/src/ui/gui_state.rs +++ b/src/ui/gui_state.rs @@ -170,6 +170,7 @@ pub enum Status { Help, Init, Logs, + SearchLogs, } /// Global gui_state, stored in an Arc @@ -411,6 +412,16 @@ impl GuiState { } } + pub fn set_logs_panel_selected(&mut self, app_data: &Arc>) { + self.selected_panel = SelectablePanel::Logs; + if (app_data.lock().get_container_len() == 0 + && self.get_selected_panel() == SelectablePanel::Commands) + || (self.log_height == 0 && self.get_selected_panel() == SelectablePanel::Logs) + { + self.selected_panel = self.selected_panel.next(); + } + self.rerender.update_draw(); + } /// Change to next selectable panel pub fn selectable_panel_next(&mut self, app_data: &Arc>) { self.selected_panel = self.selected_panel.next(); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index c46dacf..bb7a458 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -30,8 +30,8 @@ pub use self::color_match::*; pub use self::gui_state::{DeleteButton, GuiState, SelectablePanel, Status}; use crate::{ app_data::{ - AppData, Columns, ContainerId, ContainerPorts, CpuTuple, FilterBy, Header, MemTuple, - SortedOrder, State, + AppData, Columns, ContainerId, ContainerPorts, CpuTuple, FilterBy, Header, LogSearch, + MemTuple, SortedOrder, State, }, app_error::AppError, config::{AppColors, Keymap}, @@ -218,10 +218,6 @@ impl Ui { } while self.is_running.load(Ordering::SeqCst) { - // if self.redraw.get_clear() { - // self.terminal.clear().ok(); - // continue; - // } if self.should_redraw(&mut drawn_at, docker_interval_ms) { let fd = FrameData::from(&*self); @@ -287,6 +283,7 @@ impl Ui { } /// Frequent data required by multiple frame drawing functions, can reduce mutex reads by placing it all in here +/// TODO refactor this #[derive(Debug, Clone)] #[allow(clippy::struct_excessive_bools)] pub struct FrameData { @@ -294,6 +291,7 @@ pub struct FrameData { color_logs: bool, columns: Columns, container_title: String, + log_search: Option, delete_confirm: Option, filter_by: FilterBy, filter_term: Option, @@ -327,6 +325,7 @@ impl From<&Ui> for FrameData { filter_by, filter_term: filter_term.cloned(), has_containers: app_data.get_container_len() > 0, + log_search: app_data.gen_log_search(), has_error: app_data.get_error(), info_text: gui_data.info_box_text.clone(), is_loading: gui_data.is_loading(), @@ -353,9 +352,12 @@ fn draw_frame( fd: &FrameData, gui_state: &Arc>, ) { + let contains_filter = fd.status.contains(&Status::Filter); + let contains_search_logs = fd.status.contains(&Status::SearchLogs); + let whole_layout = Layout::default() .direction(Direction::Vertical) - .constraints(if fd.status.contains(&Status::Filter) { + .constraints(if contains_filter || contains_search_logs { vec![Constraint::Max(1), Constraint::Min(1), Constraint::Max(1)] } else { vec![Constraint::Max(1), Constraint::Min(1)] @@ -364,9 +366,12 @@ fn draw_frame( draw_blocks::headers::draw(whole_layout[0], colors, f, fd, gui_state, keymap); - // If required, draw filter bar if let Some(rect) = whole_layout.get(2) { - draw_blocks::filter::draw(*rect, colors, f, fd); + if contains_filter { + draw_blocks::filter::draw(*rect, colors, f, fd); + } else { + draw_blocks::search_logs::draw(*rect, colors, f, fd, keymap); + } } let upper_main = Layout::default()