feat: terminal UI for reporting jj bisect verdicts
This commit is contained in:
4 files changed
+1115
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
Generated
+600
@@ -0,0 +1,600 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "allocator-api2"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.102"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bshelper"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"ratatui",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cassowary"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "castaway"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "compact_str"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
|
||||||
|
dependencies = [
|
||||||
|
"castaway",
|
||||||
|
"cfg-if",
|
||||||
|
"itoa",
|
||||||
|
"rustversion",
|
||||||
|
"ryu",
|
||||||
|
"static_assertions",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.28.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"rustix",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"darling_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
|
||||||
|
dependencies = [
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.23.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"equivalent",
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indoc"
|
||||||
|
version = "2.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "instability"
|
||||||
|
version = "0.3.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"indoc",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.186"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.4.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ratatui"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cassowary",
|
||||||
|
"compact_str",
|
||||||
|
"crossterm",
|
||||||
|
"indoc",
|
||||||
|
"instability",
|
||||||
|
"itertools",
|
||||||
|
"lru",
|
||||||
|
"paste",
|
||||||
|
"strum",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-truncate",
|
||||||
|
"unicode-width 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.38.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||||
|
dependencies = [
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static_assertions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.26.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.26.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.117"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-truncate"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
||||||
|
dependencies = [
|
||||||
|
"itertools",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width 0.1.14",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "bshelper"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
ratatui = "0.29"
|
||||||
+506
@@ -0,0 +1,506 @@
|
|||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
|
||||||
|
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||||
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
|
use ratatui::text::{Line, Span};
|
||||||
|
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
|
||||||
|
use ratatui::{DefaultTerminal, Frame};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum Verdict {
|
||||||
|
Good,
|
||||||
|
Bad,
|
||||||
|
Skip,
|
||||||
|
Abort,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Verdict {
|
||||||
|
const ORDER: [Verdict; 4] = [Verdict::Good, Verdict::Bad, Verdict::Skip, Verdict::Abort];
|
||||||
|
|
||||||
|
fn exit_code(self) -> i32 {
|
||||||
|
match self {
|
||||||
|
Verdict::Good => 0,
|
||||||
|
Verdict::Bad => 1,
|
||||||
|
Verdict::Skip => 125,
|
||||||
|
Verdict::Abort => 127,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Verdict::Good => "GOOD",
|
||||||
|
Verdict::Bad => "BAD",
|
||||||
|
Verdict::Skip => "SKIP",
|
||||||
|
Verdict::Abort => "ABORT",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Verdict::Good => "build / test passed",
|
||||||
|
Verdict::Bad => "build / test failed",
|
||||||
|
Verdict::Skip => "can't tell — mark indecisive",
|
||||||
|
Verdict::Abort => "stop the bisection now",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color(self) -> Color {
|
||||||
|
match self {
|
||||||
|
Verdict::Good => Color::Green,
|
||||||
|
Verdict::Bad => Color::Red,
|
||||||
|
Verdict::Skip => Color::Yellow,
|
||||||
|
Verdict::Abort => Color::Gray,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn summary_emoji(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Verdict::Good => "✅",
|
||||||
|
Verdict::Bad => "❌",
|
||||||
|
Verdict::Skip => "❔",
|
||||||
|
Verdict::Abort => "⛔",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn summary_text(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Verdict::Good => "This revision was marked GOOD.",
|
||||||
|
Verdict::Bad => "This revision was marked BAD.",
|
||||||
|
Verdict::Skip => "This revision was SKIPPED.",
|
||||||
|
Verdict::Abort => "Bisection ABORTED.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ansi_color(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Verdict::Good => "32", // green
|
||||||
|
Verdict::Bad => "31", // red
|
||||||
|
Verdict::Skip => "33", // yellow
|
||||||
|
Verdict::Abort => "90", // bright black / gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum Vcs {
|
||||||
|
Jj,
|
||||||
|
Git,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vcs {
|
||||||
|
fn tag(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Vcs::Jj => "jj",
|
||||||
|
Vcs::Git => "git",
|
||||||
|
Vcs::None => "no vcs",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color(self) -> Color {
|
||||||
|
match self {
|
||||||
|
Vcs::Jj => Color::Magenta,
|
||||||
|
Vcs::Git => Color::LightRed,
|
||||||
|
Vcs::None => Color::DarkGray,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CommitInfo {
|
||||||
|
vcs: Vcs,
|
||||||
|
change_id: Option<String>,
|
||||||
|
commit_id: Option<String>,
|
||||||
|
author: Option<String>,
|
||||||
|
timestamp: Option<String>,
|
||||||
|
description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommitInfo {
|
||||||
|
fn fetch() -> Self {
|
||||||
|
// jj always wins over git when both are present.
|
||||||
|
Self::fetch_jj().or_else(Self::fetch_git).unwrap_or(Self {
|
||||||
|
vcs: Vcs::None,
|
||||||
|
change_id: None,
|
||||||
|
commit_id: None,
|
||||||
|
author: None,
|
||||||
|
timestamp: None,
|
||||||
|
description: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_jj() -> Option<Self> {
|
||||||
|
let template = concat!(
|
||||||
|
r#"change_id.short() ++ "\n""#,
|
||||||
|
r#" ++ commit_id.short() ++ "\n""#,
|
||||||
|
r#" ++ author.name() ++ "\n""#,
|
||||||
|
r#" ++ author.email() ++ "\n""#,
|
||||||
|
r#" ++ author.timestamp().format("%Y-%m-%d %H:%M") ++ "\n""#,
|
||||||
|
r#" ++ description"#,
|
||||||
|
);
|
||||||
|
let output = Command::new("jj")
|
||||||
|
.args([
|
||||||
|
"log",
|
||||||
|
"-r",
|
||||||
|
"@",
|
||||||
|
"--no-graph",
|
||||||
|
"--color",
|
||||||
|
"never",
|
||||||
|
"-T",
|
||||||
|
template,
|
||||||
|
])
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.output()
|
||||||
|
.ok()?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw = String::from_utf8_lossy(&output.stdout).into_owned();
|
||||||
|
let mut parts = raw.splitn(6, '\n');
|
||||||
|
let mut take = || {
|
||||||
|
parts
|
||||||
|
.next()
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
};
|
||||||
|
Some(Self {
|
||||||
|
vcs: Vcs::Jj,
|
||||||
|
change_id: take(),
|
||||||
|
commit_id: take(),
|
||||||
|
author: combine_author(take(), take()),
|
||||||
|
timestamp: take(),
|
||||||
|
description: take().and_then(|d| d.lines().next().map(String::from)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_git() -> Option<Self> {
|
||||||
|
let output = Command::new("git")
|
||||||
|
.args([
|
||||||
|
"log",
|
||||||
|
"-1",
|
||||||
|
"--format=%h%n%an%n%ae%n%ad%n%s",
|
||||||
|
"--date=format:%Y-%m-%d %H:%M",
|
||||||
|
])
|
||||||
|
.stderr(Stdio::null())
|
||||||
|
.output()
|
||||||
|
.ok()?;
|
||||||
|
if !output.status.success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw = String::from_utf8_lossy(&output.stdout).into_owned();
|
||||||
|
let mut parts = raw.splitn(5, '\n');
|
||||||
|
let mut take = || {
|
||||||
|
parts
|
||||||
|
.next()
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
};
|
||||||
|
Some(Self {
|
||||||
|
vcs: Vcs::Git,
|
||||||
|
change_id: None,
|
||||||
|
commit_id: take(),
|
||||||
|
author: combine_author(take(), take()),
|
||||||
|
timestamp: take(),
|
||||||
|
description: take().and_then(|d| d.lines().next().map(String::from)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn combine_author(name: Option<String>, email: Option<String>) -> Option<String> {
|
||||||
|
match (name, email) {
|
||||||
|
(Some(n), Some(e)) => Some(format!("{n} <{e}>")),
|
||||||
|
(Some(n), None) => Some(n),
|
||||||
|
(None, Some(e)) => Some(e),
|
||||||
|
(None, None) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let info = CommitInfo::fetch();
|
||||||
|
let mut terminal = ratatui::init();
|
||||||
|
let result = run(&mut terminal, &info);
|
||||||
|
ratatui::restore();
|
||||||
|
let verdict = result?;
|
||||||
|
print_verdict_summary(verdict);
|
||||||
|
std::process::exit(verdict.exit_code());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_verdict_summary(v: Verdict) {
|
||||||
|
eprintln!(
|
||||||
|
"{} \x1b[1;{}m{}\x1b[0m",
|
||||||
|
v.summary_emoji(),
|
||||||
|
v.ansi_color(),
|
||||||
|
v.summary_text(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(terminal: &mut DefaultTerminal, info: &CommitInfo) -> Result<Verdict> {
|
||||||
|
// SKIP is the safe default: a stray Enter wastes a step but doesn't
|
||||||
|
// poison the bisect.
|
||||||
|
let mut selected: usize = 2;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
terminal.draw(|f| render(f, info, selected))?;
|
||||||
|
|
||||||
|
let Event::Key(key) = event::read()? else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if key.kind != KeyEventKind::Press {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Up | KeyCode::Char('k') => {
|
||||||
|
selected = selected.saturating_sub(1);
|
||||||
|
}
|
||||||
|
KeyCode::Down | KeyCode::Char('j') => {
|
||||||
|
selected = (selected + 1).min(Verdict::ORDER.len() - 1);
|
||||||
|
}
|
||||||
|
KeyCode::Enter => return Ok(Verdict::ORDER[selected]),
|
||||||
|
// Esc, q, Ctrl-C, and everything else are deliberate no-ops.
|
||||||
|
// The only way out is to pick a verdict (ABORT included) and
|
||||||
|
// press Enter — there is no second escape hatch to advertise.
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const HPAD: u16 = 3;
|
||||||
|
const VPAD: u16 = 1;
|
||||||
|
const VERDICT_LABEL_WIDTH: usize = 5; // widest label, ABORT
|
||||||
|
|
||||||
|
fn render(frame: &mut Frame, info: &CommitInfo, selected: usize) {
|
||||||
|
let term = frame.area();
|
||||||
|
|
||||||
|
let header_lines = build_header_lines(info);
|
||||||
|
let footer_line = build_footer_line(selected);
|
||||||
|
|
||||||
|
let header_w = header_lines.iter().map(Line::width).max().unwrap_or(0);
|
||||||
|
let verdict_w = Verdict::ORDER
|
||||||
|
.iter()
|
||||||
|
.map(|v| verdict_row_width(*v))
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
// Width is computed against the widest footer (ABORT label) so the
|
||||||
|
// modal stays the same size as the user navigates between verdicts.
|
||||||
|
let footer_w = (0..Verdict::ORDER.len())
|
||||||
|
.map(|i| build_footer_line(i).width())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
let content_w = header_w.max(verdict_w).max(footer_w);
|
||||||
|
|
||||||
|
let inner_w = (content_w as u16) + HPAD * 2;
|
||||||
|
let inner_h: u16 = VPAD // top pad
|
||||||
|
+ header_lines.len() as u16 // header
|
||||||
|
+ 1 // blank
|
||||||
|
+ 1 // separator
|
||||||
|
+ 1 // blank
|
||||||
|
+ Verdict::ORDER.len() as u16 // verdicts
|
||||||
|
+ 1 // blank
|
||||||
|
+ 1 // separator
|
||||||
|
+ 1 // footer
|
||||||
|
+ VPAD; // bottom pad
|
||||||
|
|
||||||
|
let modal_w = (inner_w + 2).min(term.width);
|
||||||
|
let modal_h = (inner_h + 2).min(term.height);
|
||||||
|
let modal = centered_rect(term, modal_w, modal_h);
|
||||||
|
|
||||||
|
frame.render_widget(Clear, modal);
|
||||||
|
|
||||||
|
let title_line = Line::from(vec![
|
||||||
|
Span::raw(" bshelper "),
|
||||||
|
Span::styled(
|
||||||
|
format!("· {} ", info.vcs.tag()),
|
||||||
|
Style::default()
|
||||||
|
.fg(info.vcs.color())
|
||||||
|
.add_modifier(Modifier::BOLD),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
let block = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(Style::default().fg(Color::DarkGray))
|
||||||
|
.title(title_line)
|
||||||
|
.title_alignment(Alignment::Center);
|
||||||
|
let inner = block.inner(modal);
|
||||||
|
frame.render_widget(block, modal);
|
||||||
|
|
||||||
|
let chunks = Layout::vertical([
|
||||||
|
Constraint::Length(VPAD),
|
||||||
|
Constraint::Length(header_lines.len() as u16),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(Verdict::ORDER.len() as u16),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Length(VPAD),
|
||||||
|
])
|
||||||
|
.split(inner);
|
||||||
|
|
||||||
|
frame.render_widget(Paragraph::new(header_lines), pad(chunks[1]));
|
||||||
|
render_separator(frame, pad(chunks[3]));
|
||||||
|
|
||||||
|
let verdict_rect = pad(chunks[5]);
|
||||||
|
let verdict_rows =
|
||||||
|
Layout::vertical([Constraint::Length(1); Verdict::ORDER.len()]).split(verdict_rect);
|
||||||
|
for (i, v) in Verdict::ORDER.iter().enumerate() {
|
||||||
|
render_verdict_row(frame, verdict_rows[i], *v, i == selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
render_separator(frame, pad(chunks[7]));
|
||||||
|
let footer_rect = pad(chunks[8]);
|
||||||
|
frame.render_widget(
|
||||||
|
Paragraph::new(footer_line).alignment(Alignment::Center),
|
||||||
|
footer_rect,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pad(r: Rect) -> Rect {
|
||||||
|
Rect {
|
||||||
|
x: r.x + HPAD,
|
||||||
|
y: r.y,
|
||||||
|
width: r.width.saturating_sub(HPAD * 2),
|
||||||
|
height: r.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn centered_rect(area: Rect, w: u16, h: u16) -> Rect {
|
||||||
|
let w = w.min(area.width);
|
||||||
|
let h = h.min(area.height);
|
||||||
|
let x = area.x + (area.width.saturating_sub(w)) / 2;
|
||||||
|
let y = area.y + (area.height.saturating_sub(h)) / 2;
|
||||||
|
Rect {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_header_lines(info: &CommitInfo) -> Vec<Line<'static>> {
|
||||||
|
let label_style = Style::default().fg(Color::DarkGray);
|
||||||
|
let id_style = Style::default()
|
||||||
|
.fg(Color::Magenta)
|
||||||
|
.add_modifier(Modifier::BOLD);
|
||||||
|
let hash_style = Style::default().fg(Color::Cyan);
|
||||||
|
|
||||||
|
let mut lines: Vec<Line> = Vec::new();
|
||||||
|
|
||||||
|
let commit_line = match (&info.change_id, &info.commit_id, info.vcs) {
|
||||||
|
(Some(c), Some(h), _) => Line::from(vec![
|
||||||
|
Span::styled("Commit ", label_style),
|
||||||
|
Span::styled(c.clone(), id_style),
|
||||||
|
Span::raw(" "),
|
||||||
|
Span::styled(h.clone(), hash_style),
|
||||||
|
]),
|
||||||
|
(None, Some(h), _) => Line::from(vec![
|
||||||
|
Span::styled("Commit ", label_style),
|
||||||
|
Span::styled(h.clone(), hash_style),
|
||||||
|
]),
|
||||||
|
_ => Line::from(vec![
|
||||||
|
Span::styled("Commit ", label_style),
|
||||||
|
Span::styled("(no revision info)", label_style),
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
lines.push(commit_line);
|
||||||
|
|
||||||
|
if let Some(author) = &info.author {
|
||||||
|
lines.push(Line::from(vec![
|
||||||
|
Span::styled("Author ", label_style),
|
||||||
|
Span::raw(author.clone()),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
if let Some(ts) = &info.timestamp {
|
||||||
|
lines.push(Line::from(vec![
|
||||||
|
Span::styled("Date ", label_style),
|
||||||
|
Span::raw(ts.clone()),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(Line::from(""));
|
||||||
|
|
||||||
|
let desc_text = info.description.clone().unwrap_or_else(|| {
|
||||||
|
if info.vcs == Vcs::None {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
"(no description)".to_string()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if !desc_text.is_empty() {
|
||||||
|
lines.push(Line::from(Span::styled(
|
||||||
|
desc_text,
|
||||||
|
Style::default().add_modifier(Modifier::ITALIC),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verdict_row_width(_v: Verdict) -> usize {
|
||||||
|
// " ▶ {label:>5} {description} "
|
||||||
|
let max_desc = Verdict::ORDER
|
||||||
|
.iter()
|
||||||
|
.map(|v| v.description().chars().count())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
2 + 1 + 2 + VERDICT_LABEL_WIDTH + 3 + max_desc + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_verdict_row(frame: &mut Frame, area: Rect, v: Verdict, is_selected: bool) {
|
||||||
|
if is_selected {
|
||||||
|
let content = format!(
|
||||||
|
" ▶ {:>w$} {}",
|
||||||
|
v.label(),
|
||||||
|
v.description(),
|
||||||
|
w = VERDICT_LABEL_WIDTH
|
||||||
|
);
|
||||||
|
let style = Style::default()
|
||||||
|
.fg(Color::Black)
|
||||||
|
.bg(v.color())
|
||||||
|
.add_modifier(Modifier::BOLD);
|
||||||
|
frame.render_widget(Paragraph::new(content).style(style), area);
|
||||||
|
} else {
|
||||||
|
let line = Line::from(vec![
|
||||||
|
Span::raw(" "),
|
||||||
|
Span::styled(
|
||||||
|
format!("{:>w$}", v.label(), w = VERDICT_LABEL_WIDTH),
|
||||||
|
Style::default().fg(v.color()).add_modifier(Modifier::BOLD),
|
||||||
|
),
|
||||||
|
Span::raw(" "),
|
||||||
|
Span::styled(v.description(), Style::default().fg(Color::DarkGray)),
|
||||||
|
]);
|
||||||
|
frame.render_widget(Paragraph::new(line), area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_separator(frame: &mut Frame, area: Rect) {
|
||||||
|
let line = "─".repeat(area.width as usize);
|
||||||
|
frame.render_widget(
|
||||||
|
Paragraph::new(line).style(Style::default().fg(Color::DarkGray)),
|
||||||
|
area,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_footer_line(selected: usize) -> Line<'static> {
|
||||||
|
let v = Verdict::ORDER[selected];
|
||||||
|
let dim = Style::default().fg(Color::DarkGray);
|
||||||
|
let kbd = Style::default()
|
||||||
|
.fg(Color::White)
|
||||||
|
.add_modifier(Modifier::BOLD);
|
||||||
|
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled("↑/k", kbd),
|
||||||
|
Span::styled(" ", dim),
|
||||||
|
Span::styled("↓/j", kbd),
|
||||||
|
Span::styled(" navigate · ", dim),
|
||||||
|
Span::styled("Enter", kbd),
|
||||||
|
Span::styled(" commits: ", dim),
|
||||||
|
Span::styled(
|
||||||
|
format!("{:<width$}", v.label(), width = VERDICT_LABEL_WIDTH),
|
||||||
|
Style::default().fg(v.color()).add_modifier(Modifier::BOLD),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
}
|
||||||
Reference in new issue
Block a user