mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-06 03:25:23 +00:00
feat: send to channel
This commit is contained in:
parent
ddedbc11da
commit
d0d453fe97
@ -7,8 +7,9 @@ ctrl-p = "SelectPrevEntry"
|
||||
ctrl-d = "ScrollPreviewHalfPageDown"
|
||||
ctrl-u = "ScrollPreviewHalfPageUp"
|
||||
enter = "SelectEntry"
|
||||
ctrl-enter = "SendToChannel"
|
||||
ctrl-y = "CopyEntryToClipboard"
|
||||
ctrl-s = "ToggleRemoteControl"
|
||||
alt-s = "ToggleSendToChannel"
|
||||
|
||||
[keybindings.RemoteControl]
|
||||
esc = "Quit"
|
||||
@ -18,3 +19,12 @@ ctrl-n = "SelectNextEntry"
|
||||
ctrl-p = "SelectPrevEntry"
|
||||
enter = "SelectEntry"
|
||||
ctrl-s = "ToggleRemoteControl"
|
||||
|
||||
[keybindings.SendToChannel]
|
||||
esc = "Quit"
|
||||
down = "SelectNextEntry"
|
||||
up = "SelectPrevEntry"
|
||||
ctrl-n = "SelectNextEntry"
|
||||
ctrl-p = "SelectPrevEntry"
|
||||
enter = "SelectEntry"
|
||||
alt-s = "ToggleSendToChannel"
|
||||
|
374
Cargo.lock
generated
374
Cargo.lock
generated
@ -195,6 +195,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
@ -227,6 +233,32 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"log",
|
||||
"polling",
|
||||
"rustix",
|
||||
"slab",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop-wayland-source"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
|
||||
dependencies = [
|
||||
"calloop",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.9"
|
||||
@ -343,6 +375,16 @@ version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342"
|
||||
dependencies = [
|
||||
"lazy-bytes-cast",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clru"
|
||||
version = "0.6.2"
|
||||
@ -397,6 +439,15 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.14.1"
|
||||
@ -457,6 +508,20 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "copypasta"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "deb85422867ca93da58b7f95fb5c0c10f6183ed6e1ef8841568968a896d3a858"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"objc",
|
||||
"objc-foundation",
|
||||
"objc_id",
|
||||
"smithay-clipboard",
|
||||
"x11-clipboard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.14"
|
||||
@ -543,6 +608,12 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cursor-icon"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.10"
|
||||
@ -675,6 +746,15 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.5.2"
|
||||
@ -684,6 +764,12 @@ dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
@ -902,6 +988,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
@ -1468,6 +1564,12 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.9"
|
||||
@ -1623,6 +1725,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy-bytes-cast"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@ -1635,6 +1743,16 @@ version = "0.2.161"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
@ -1683,6 +1801,15 @@ dependencies = [
|
||||
"hashbrown 0.15.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "malloc_buf"
|
||||
version = "0.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
@ -1737,7 +1864,7 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
@ -1800,6 +1927,35 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
|
||||
dependencies = [
|
||||
"malloc_buf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-foundation"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
|
||||
dependencies = [
|
||||
"block",
|
||||
"objc",
|
||||
"objc_id",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc_id"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
|
||||
dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.2"
|
||||
@ -1988,11 +2144,26 @@ checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap",
|
||||
"quick-xml",
|
||||
"quick-xml 0.32.0",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi 0.4.0",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@ -2033,6 +2204,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
@ -2187,9 +2367,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.37"
|
||||
version = "0.38.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
|
||||
checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"errno",
|
||||
@ -2219,6 +2399,12 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@ -2236,18 +2422,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.213"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.213"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2352,6 +2538,42 @@ version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"calloop",
|
||||
"calloop-wayland-source",
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
"log",
|
||||
"memmap2",
|
||||
"rustix",
|
||||
"thiserror",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-csd-frame",
|
||||
"wayland-cursor",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
"wayland-scanner",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smithay-clipboard"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"smithay-client-toolkit",
|
||||
"wayland-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.7"
|
||||
@ -2458,6 +2680,7 @@ dependencies = [
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"config",
|
||||
"copypasta",
|
||||
"crossterm",
|
||||
"derive_deref",
|
||||
"devicons",
|
||||
@ -2942,6 +3165,102 @@ version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"downcast-rs",
|
||||
"rustix",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.31.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-csd-frame"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cursor-icon",
|
||||
"wayland-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-cursor"
|
||||
version = "0.31.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"wayland-client",
|
||||
"xcursor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.32.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd0ade57c4e6e9a8952741325c30bf82f4246885dca8bf561898b86d0c1f58e"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-wlr"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "782e12f6cd923c3c316130d56205ebab53f55d6666b7faddfad36cecaeeb4022"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml 0.36.2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.31.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"log",
|
||||
"once_cell",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@ -3130,6 +3449,45 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11-clipboard"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"rustix",
|
||||
"x11rb-protocol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x11rb-protocol"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
|
||||
|
||||
[[package]]
|
||||
name = "xcursor"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61"
|
||||
|
||||
[[package]]
|
||||
name = "xkeysym"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
|
@ -67,6 +67,7 @@ unicode-width = "0.2.0"
|
||||
human-panic = "2.0.2"
|
||||
pretty_assertions = "1.4.1"
|
||||
termtree = "0.5.1"
|
||||
copypasta = "0.10.1"
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
@ -86,7 +87,7 @@ debug = true
|
||||
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
lto = "fat"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { version = "0.28.1", features = ["serde", "use-dev-tty"] }
|
||||
|
@ -20,6 +20,7 @@ pub enum Action {
|
||||
SelectAndExit,
|
||||
SelectNextEntry,
|
||||
SelectPrevEntry,
|
||||
CopyEntryToClipboard,
|
||||
// navigation actions
|
||||
GoToPaneUp,
|
||||
GoToPaneDown,
|
||||
@ -43,5 +44,5 @@ pub enum Action {
|
||||
NoOp,
|
||||
// channel actions
|
||||
ToggleRemoteControl,
|
||||
SendToChannel,
|
||||
ToggleSendToChannel,
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ use color_eyre::Result;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use crate::channels::{CliTvChannel, TelevisionChannel};
|
||||
use crate::channels::TelevisionChannel;
|
||||
use crate::television::{Mode, Television};
|
||||
use crate::{
|
||||
action::Action,
|
||||
@ -234,6 +234,7 @@ impl App {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// forward action to the television handler
|
||||
if let Some(action) =
|
||||
self.television.lock().await.update(action.clone()).await?
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::entry::Entry;
|
||||
use color_eyre::eyre::Result;
|
||||
use television_derive::{Broadcast, CliChannel, UnitChannel};
|
||||
use television_derive::{Broadcast, ToCliChannel, ToUnitChannel};
|
||||
|
||||
mod alias;
|
||||
mod env;
|
||||
@ -104,7 +104,7 @@ pub trait OnAir: Send {
|
||||
/// of carrying the actual channel instances around. It also generates the necessary
|
||||
/// glue code to automatically create a channel instance from the selected enum variant.
|
||||
#[allow(dead_code, clippy::module_name_repetitions)]
|
||||
#[derive(UnitChannel, CliChannel, Broadcast)]
|
||||
#[derive(ToUnitChannel, ToCliChannel, Broadcast)]
|
||||
pub enum TelevisionChannel {
|
||||
/// The environment variables channel.
|
||||
///
|
||||
@ -134,11 +134,125 @@ pub enum TelevisionChannel {
|
||||
/// The remote control channel.
|
||||
///
|
||||
/// This channel allows to switch between different channels.
|
||||
#[exclude_from_unit]
|
||||
#[exclude_from_cli]
|
||||
RemoteControl(remote_control::RemoteControl),
|
||||
}
|
||||
|
||||
/// NOTE: this could be generated by a derive macro
|
||||
macro_rules! variant_to_module {
|
||||
(Files) => {
|
||||
files::Channel
|
||||
};
|
||||
(Text) => {
|
||||
text::Channel
|
||||
};
|
||||
(GitRepos) => {
|
||||
git_repos::Channel
|
||||
};
|
||||
(Env) => {
|
||||
env::Channel
|
||||
};
|
||||
(Stdin) => {
|
||||
stdin::Channel
|
||||
};
|
||||
(Alias) => {
|
||||
alias::Channel
|
||||
};
|
||||
(RemoteControl) => {
|
||||
remote_control::RemoteControl
|
||||
};
|
||||
}
|
||||
|
||||
/// A macro that generates two methods for the `TelevisionChannel` enum based on
|
||||
/// the transitions defined in the macro call.
|
||||
///
|
||||
/// The first method `available_transitions` returns a list of possible transitions
|
||||
/// from the current channel.
|
||||
///
|
||||
/// The second method `transition_to` transitions from the current channel to the
|
||||
/// target channel.
|
||||
///
|
||||
/// # Example
|
||||
/// The following example defines transitions from the `Files` channel to the `Text`
|
||||
/// channel and from the `GitRepos` channel to the `Files` and `Text` channels.
|
||||
/// ```rust
|
||||
/// define_transitions! {
|
||||
/// // The `Files` channel can transition to the `Text` channel.
|
||||
/// Files => [Text],
|
||||
/// // The `GitRepos` channel can transition to the `Files` and `Text` channels.
|
||||
/// GitRepos => [Files, Text],
|
||||
/// }
|
||||
/// ```
|
||||
/// This will generate the following methods for the `TelevisionChannel` enum:
|
||||
/// ```rust
|
||||
/// impl TelevisionChannel {
|
||||
/// pub fn available_transitions(&self) -> Vec<UnitChannel> {
|
||||
/// match self {
|
||||
/// TelevisionChannel::Files(_) => vec![UnitChannel::Text],
|
||||
/// TelevisionChannel::GitRepos(_) => vec![UnitChannel::Files, UnitChannel::Text],
|
||||
/// _ => Vec::new(),
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// pub fn transition_to(self, target: UnitChannel) -> TelevisionChannel {
|
||||
/// match (self, target) {
|
||||
/// (tv_channel @ TelevisionChannel::Files(_), UnitChannel::Text) => {
|
||||
/// TelevisionChannel::Text(text::Channel::from(tv_channel))
|
||||
/// },
|
||||
/// (tv_channel @ TelevisionChannel::GitRepos(_), UnitChannel::Files) => {
|
||||
/// TelevisionChannel::Files(files::Channel::from(tv_channel))
|
||||
/// },
|
||||
/// (tv_channel @ TelevisionChannel::GitRepos(_), UnitChannel::Text) => {
|
||||
/// TelevisionChannel::Text(text::Channel::from(tv_channel))
|
||||
/// },
|
||||
/// _ => unreachable!(),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
///
|
||||
macro_rules! define_transitions {
|
||||
(
|
||||
$(
|
||||
$from_variant:ident => [ $($to_variant:ident),* $(,)? ],
|
||||
)*
|
||||
) => {
|
||||
impl TelevisionChannel {
|
||||
pub fn available_transitions(&self) -> Vec<UnitChannel> {
|
||||
match self {
|
||||
$(
|
||||
TelevisionChannel::$from_variant(_) => vec![
|
||||
$( UnitChannel::$to_variant ),*
|
||||
],
|
||||
)*
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transition_to(&mut self, target: UnitChannel) -> TelevisionChannel {
|
||||
match (self, target) {
|
||||
$(
|
||||
$(
|
||||
(tv_channel @ TelevisionChannel::$from_variant(_), UnitChannel::$to_variant) => {
|
||||
TelevisionChannel::$to_variant(
|
||||
<variant_to_module!($to_variant)>::from(tv_channel)
|
||||
)
|
||||
},
|
||||
)*
|
||||
)*
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_transitions! {
|
||||
Files => [Text],
|
||||
GitRepos => [Files, Text],
|
||||
}
|
||||
|
||||
/// NOTE: this could/should be generated by a macro
|
||||
impl TryFrom<&Entry> for TelevisionChannel {
|
||||
type Error = String;
|
||||
|
||||
|
@ -52,19 +52,19 @@ impl Default for Channel {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TelevisionChannel> for Channel {
|
||||
fn from(channel: TelevisionChannel) -> Self {
|
||||
match channel {
|
||||
TelevisionChannel::Files(channel) => channel,
|
||||
TelevisionChannel::GitRepos(mut channel) => {
|
||||
let git_project_paths = channel
|
||||
.results(channel.result_count(), 0)
|
||||
.iter()
|
||||
.map(|entry| PathBuf::from(entry.name.clone()))
|
||||
.collect();
|
||||
Self::new(git_project_paths)
|
||||
impl From<&mut TelevisionChannel> for Channel {
|
||||
fn from(value: &mut TelevisionChannel) -> Self {
|
||||
match value {
|
||||
c @ TelevisionChannel::GitRepos(_) => {
|
||||
let entries = c.results(c.result_count(), 0);
|
||||
Self::new(
|
||||
entries
|
||||
.iter()
|
||||
.map(|entry| PathBuf::from(entry.name.clone()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
_ => Channel::default(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -166,10 +166,10 @@ async fn load_files(paths: Vec<PathBuf>, injector: Injector<String>) {
|
||||
if let Ok(entry) = result {
|
||||
if entry.file_type().unwrap().is_file() {
|
||||
let file_path = preprocess_line(
|
||||
&*entry
|
||||
&entry
|
||||
.path()
|
||||
.strip_prefix(¤t_dir)
|
||||
.unwrap()
|
||||
.unwrap_or(entry.path())
|
||||
.to_string_lossy(),
|
||||
);
|
||||
let _ = injector.push(file_path, |e, cols| {
|
||||
|
@ -7,6 +7,7 @@ use nucleo::{
|
||||
Config, Nucleo,
|
||||
};
|
||||
|
||||
use crate::channels::{TelevisionChannel, UnitChannel};
|
||||
use crate::{
|
||||
channels::{CliTvChannel, OnAir},
|
||||
entry::Entry,
|
||||
@ -15,7 +16,7 @@ use crate::{
|
||||
};
|
||||
|
||||
pub struct RemoteControl {
|
||||
matcher: Nucleo<CliTvChannel>,
|
||||
matcher: Nucleo<String>,
|
||||
last_pattern: String,
|
||||
result_count: u32,
|
||||
total_count: u32,
|
||||
@ -25,7 +26,7 @@ pub struct RemoteControl {
|
||||
const NUM_THREADS: usize = 1;
|
||||
|
||||
impl RemoteControl {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(channels: Vec<UnitChannel>) -> Self {
|
||||
let matcher = Nucleo::new(
|
||||
Config::DEFAULT,
|
||||
Arc::new(|| {}),
|
||||
@ -33,9 +34,9 @@ impl RemoteControl {
|
||||
1,
|
||||
);
|
||||
let injector = matcher.injector();
|
||||
for variant in CliTvChannel::value_variants() {
|
||||
let _ = injector.push(*variant, |e, cols| {
|
||||
cols[0] = (*e).to_string().into();
|
||||
for channel in channels {
|
||||
let _ = injector.push(channel.to_string(), |e, cols| {
|
||||
cols[0] = e.clone().into();
|
||||
});
|
||||
}
|
||||
RemoteControl {
|
||||
@ -47,12 +48,23 @@ impl RemoteControl {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_transitions_from(
|
||||
television_channel: &TelevisionChannel,
|
||||
) -> Self {
|
||||
Self::new(television_channel.available_transitions())
|
||||
}
|
||||
|
||||
const MATCHER_TICK_TIMEOUT: u64 = 2;
|
||||
}
|
||||
|
||||
impl Default for RemoteControl {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self::new(
|
||||
CliTvChannel::value_variants()
|
||||
.iter()
|
||||
.map(|v| v.to_string().as_str().into())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +115,7 @@ impl OnAir for RemoteControl {
|
||||
let indices = indices.drain(..);
|
||||
|
||||
let name = item.matcher_columns[0].to_string();
|
||||
Entry::new(name.clone(), PreviewType::Basic)
|
||||
Entry::new(name, PreviewType::Basic)
|
||||
.with_name_match_ranges(
|
||||
indices.map(|i| (i, i + 1)).collect(),
|
||||
)
|
||||
|
@ -1,4 +1,5 @@
|
||||
use devicons::FileIcon;
|
||||
use ignore::WalkState;
|
||||
use nucleo::{
|
||||
pattern::{CaseMatching, Normalization},
|
||||
Config, Injector, Nucleo,
|
||||
@ -8,11 +9,11 @@ use std::{
|
||||
io::{BufRead, Read, Seek},
|
||||
path::{Path, PathBuf},
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
u32,
|
||||
};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use tracing::{debug, info};
|
||||
|
||||
use super::OnAir;
|
||||
use super::{OnAir, TelevisionChannel};
|
||||
use crate::previewers::PreviewType;
|
||||
use crate::utils::{
|
||||
files::{is_not_text, walk_builder, DEFAULT_NUM_THREADS},
|
||||
@ -51,11 +52,11 @@ pub struct Channel {
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn new(working_dir: &Path) -> Self {
|
||||
pub fn new(directories: Vec<PathBuf>) -> Self {
|
||||
let matcher = Nucleo::new(Config::DEFAULT, Arc::new(|| {}), None, 1);
|
||||
// start loading files in the background
|
||||
let crawl_handle = tokio::spawn(load_candidates(
|
||||
working_dir.to_path_buf(),
|
||||
let crawl_handle = tokio::spawn(crawl_for_candidates(
|
||||
directories,
|
||||
matcher.injector(),
|
||||
));
|
||||
Channel {
|
||||
@ -68,12 +69,88 @@ impl Channel {
|
||||
}
|
||||
}
|
||||
|
||||
fn from_file_paths(file_paths: Vec<PathBuf>) -> Self {
|
||||
let matcher = Nucleo::new(
|
||||
Config::DEFAULT.match_paths(),
|
||||
Arc::new(|| {}),
|
||||
None,
|
||||
1,
|
||||
);
|
||||
let injector = matcher.injector();
|
||||
let current_dir = std::env::current_dir().unwrap();
|
||||
let crawl_handle = tokio::spawn(async move {
|
||||
let mut lines_in_mem = 0;
|
||||
for path in file_paths {
|
||||
if lines_in_mem > MAX_LINES_IN_MEM {
|
||||
break;
|
||||
}
|
||||
if let Some(injected_lines) =
|
||||
try_inject_lines(injector.clone(), ¤t_dir, &path)
|
||||
{
|
||||
lines_in_mem += injected_lines;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Channel {
|
||||
matcher,
|
||||
last_pattern: String::new(),
|
||||
result_count: 0,
|
||||
total_count: 0,
|
||||
running: false,
|
||||
crawl_handle,
|
||||
}
|
||||
}
|
||||
|
||||
const MATCHER_TICK_TIMEOUT: u64 = 2;
|
||||
}
|
||||
|
||||
impl Default for Channel {
|
||||
fn default() -> Self {
|
||||
Self::new(&std::env::current_dir().unwrap())
|
||||
Self::new(vec![std::env::current_dir().unwrap()])
|
||||
}
|
||||
}
|
||||
|
||||
/// Since we're limiting the number of lines in memory, it makes sense to also limit the number of files
|
||||
/// we're willing to search in when piping from the `Files` channel.
|
||||
/// This prevents blocking the UI for too long when piping from a channel with a lot of files.
|
||||
///
|
||||
/// This should be calculated based on the number of lines we're willing to keep in memory:
|
||||
/// `MAX_LINES_IN_MEM / 100` (assuming 100 lines per file on average).
|
||||
const MAX_PIPED_FILES: usize = MAX_LINES_IN_MEM / 200;
|
||||
|
||||
impl From<&mut TelevisionChannel> for Channel {
|
||||
fn from(value: &mut TelevisionChannel) -> Self {
|
||||
match value {
|
||||
c @ TelevisionChannel::Files(_) => {
|
||||
let entries = c.results(
|
||||
c.result_count().min(
|
||||
u32::try_from(MAX_PIPED_FILES).unwrap_or(u32::MAX),
|
||||
),
|
||||
0,
|
||||
);
|
||||
Self::from_file_paths(
|
||||
entries
|
||||
.iter()
|
||||
.flat_map(|entry| {
|
||||
PathBuf::from(entry.name.clone()).canonicalize()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
c @ TelevisionChannel::GitRepos(_) => {
|
||||
let entries = c.results(c.result_count(), 0);
|
||||
Self::new(
|
||||
entries
|
||||
.iter()
|
||||
.flat_map(|entry| {
|
||||
PathBuf::from(entry.name.clone()).canonicalize()
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,7 +183,7 @@ impl OnAir for Channel {
|
||||
.matched_items(
|
||||
offset
|
||||
..(num_entries + offset)
|
||||
.min(snapshot.matched_item_count()),
|
||||
.min(snapshot.matched_item_count()),
|
||||
)
|
||||
.map(move |item| {
|
||||
snapshot.pattern().column_pattern(0).indices(
|
||||
@ -125,11 +202,11 @@ impl OnAir for Channel {
|
||||
display_path.clone() + &item.data.line_number.to_string(),
|
||||
PreviewType::Files,
|
||||
)
|
||||
.with_display_name(display_path)
|
||||
.with_value(line)
|
||||
.with_value_match_ranges(indices.map(|i| (i, i + 1)).collect())
|
||||
.with_icon(FileIcon::from(item.data.path.as_path()))
|
||||
.with_line_number(item.data.line_number)
|
||||
.with_display_name(display_path)
|
||||
.with_value(line)
|
||||
.with_value_match_ranges(indices.map(|i| (i, i + 1)).collect())
|
||||
.with_icon(FileIcon::from(item.data.path.as_path()))
|
||||
.with_line_number(item.data.line_number)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@ -184,98 +261,120 @@ const MAX_FILE_SIZE: u64 = 4 * 1024 * 1024;
|
||||
///
|
||||
/// A typical line should take somewhere around 100 bytes in memory (for utf8 english text),
|
||||
/// so this should take around 100 x `5_000_000` = 500MB of memory.
|
||||
const MAX_IN_MEMORY_LINES: usize = 5_000_000;
|
||||
const MAX_LINES_IN_MEM: usize = 5_000_000;
|
||||
|
||||
#[allow(clippy::unused_async)]
|
||||
async fn load_candidates(path: PathBuf, injector: Injector<CandidateLine>) {
|
||||
async fn crawl_for_candidates(
|
||||
directories: Vec<PathBuf>,
|
||||
injector: Injector<CandidateLine>,
|
||||
) {
|
||||
if directories.is_empty() {
|
||||
return;
|
||||
}
|
||||
let current_dir = std::env::current_dir().unwrap();
|
||||
let walker =
|
||||
walk_builder(&path, *DEFAULT_NUM_THREADS, None, None).build_parallel();
|
||||
let mut walker =
|
||||
walk_builder(&directories[0], *DEFAULT_NUM_THREADS, None, None);
|
||||
for path in directories[1..].iter() {
|
||||
walker.add(path);
|
||||
}
|
||||
|
||||
let lines_in_mem = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
walker.run(|| {
|
||||
walker.build_parallel().run(|| {
|
||||
let injector = injector.clone();
|
||||
let current_dir = current_dir.clone();
|
||||
let lines_in_mem = lines_in_mem.clone();
|
||||
Box::new(move |result| {
|
||||
if lines_in_mem.load(std::sync::atomic::Ordering::Relaxed) > MAX_IN_MEMORY_LINES {
|
||||
return ignore::WalkState::Quit;
|
||||
if lines_in_mem.load(std::sync::atomic::Ordering::Relaxed)
|
||||
> MAX_LINES_IN_MEM
|
||||
{
|
||||
return WalkState::Quit;
|
||||
}
|
||||
if let Ok(entry) = result {
|
||||
if entry.file_type().unwrap().is_file() {
|
||||
if let Ok(m) = entry.metadata() {
|
||||
if m.len() > MAX_FILE_SIZE {
|
||||
return ignore::WalkState::Continue;
|
||||
return WalkState::Continue;
|
||||
}
|
||||
}
|
||||
// iterate over the lines of the file
|
||||
match File::open(entry.path()) {
|
||||
Ok(file) => {
|
||||
// is the file a text-based file?
|
||||
let mut reader = std::io::BufReader::new(&file);
|
||||
let mut buffer = [0u8; 128];
|
||||
match reader.read(&mut buffer) {
|
||||
Ok(bytes_read) => {
|
||||
if (bytes_read == 0)
|
||||
|| is_not_text(&buffer)
|
||||
.unwrap_or(false)
|
||||
|| proportion_of_printable_ascii_characters(&buffer)
|
||||
< PRINTABLE_ASCII_THRESHOLD
|
||||
{
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
reader
|
||||
.seek(std::io::SeekFrom::Start(0))
|
||||
.unwrap();
|
||||
}
|
||||
Err(_) => {
|
||||
return ignore::WalkState::Continue;
|
||||
}
|
||||
}
|
||||
// read the lines of the file
|
||||
let mut line_number = 0;
|
||||
for maybe_line in reader.lines() {
|
||||
match maybe_line {
|
||||
Ok(l) => {
|
||||
line_number += 1;
|
||||
let line = preprocess_line(&l);
|
||||
if line.is_empty() {
|
||||
debug!("Empty line");
|
||||
continue;
|
||||
}
|
||||
let candidate = CandidateLine::new(
|
||||
entry
|
||||
.path()
|
||||
.strip_prefix(¤t_dir)
|
||||
.unwrap()
|
||||
.to_path_buf(),
|
||||
line,
|
||||
line_number,
|
||||
);
|
||||
let _ = injector.push(
|
||||
candidate,
|
||||
|c, cols| {
|
||||
cols[0] =
|
||||
c.line.clone().into();
|
||||
},
|
||||
);
|
||||
lines_in_mem.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
Err(e) => {
|
||||
info!("Error reading line: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
info!("Error opening file: {:?}", e);
|
||||
}
|
||||
// try to inject the lines of the file
|
||||
if let Some(injected_lines) = try_inject_lines(
|
||||
injector.clone(),
|
||||
¤t_dir,
|
||||
entry.path(),
|
||||
) {
|
||||
lines_in_mem.fetch_add(
|
||||
injected_lines,
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ignore::WalkState::Continue
|
||||
WalkState::Continue
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn try_inject_lines(
|
||||
injector: Injector<CandidateLine>,
|
||||
current_dir: &PathBuf,
|
||||
path: &Path,
|
||||
) -> Option<usize> {
|
||||
match File::open(path) {
|
||||
Ok(file) => {
|
||||
// is the file a text-based file?
|
||||
let mut reader = std::io::BufReader::new(&file);
|
||||
let mut buffer = [0u8; 128];
|
||||
match reader.read(&mut buffer) {
|
||||
Ok(bytes_read) => {
|
||||
if (bytes_read == 0)
|
||||
|| is_not_text(&buffer).unwrap_or(false)
|
||||
|| proportion_of_printable_ascii_characters(&buffer)
|
||||
< PRINTABLE_ASCII_THRESHOLD
|
||||
{
|
||||
return None;
|
||||
}
|
||||
reader.seek(std::io::SeekFrom::Start(0)).unwrap();
|
||||
}
|
||||
Err(_) => {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// read the lines of the file
|
||||
let mut line_number = 0;
|
||||
let mut injected_lines = 0;
|
||||
for maybe_line in reader.lines() {
|
||||
match maybe_line {
|
||||
Ok(l) => {
|
||||
line_number += 1;
|
||||
let line = preprocess_line(&l);
|
||||
if line.is_empty() {
|
||||
debug!("Empty line");
|
||||
continue;
|
||||
}
|
||||
let candidate = CandidateLine::new(
|
||||
path.strip_prefix(¤t_dir)
|
||||
.unwrap_or(path)
|
||||
.to_path_buf(),
|
||||
line,
|
||||
line_number,
|
||||
);
|
||||
let _ = injector.push(candidate, |c, cols| {
|
||||
cols[0] = c.line.clone().into();
|
||||
});
|
||||
injected_lines += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Error reading line: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(injected_lines)
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Error opening file {:?}: {:?}", path, e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
use devicons::FileIcon;
|
||||
|
||||
use crate::previewers::PreviewType;
|
||||
use crate::utils::strings::preprocess_line;
|
||||
|
||||
/// NOTE: having an enum for entry types would be nice since it would allow
|
||||
/// having a nicer implementation for transitions between channels. This would
|
||||
/// permit implementing `From<EntryType>` for channels which would make the
|
||||
/// channel convertible from any other that yields `EntryType`.
|
||||
/// This needs pondering since it does bring another level of abstraction and
|
||||
/// adds a layer of complexity.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct Entry {
|
||||
pub name: String,
|
||||
@ -18,7 +23,7 @@ pub struct Entry {
|
||||
impl Entry {
|
||||
pub fn new(name: String, preview_type: PreviewType) -> Self {
|
||||
Self {
|
||||
name: preprocess_line(&name),
|
||||
name,
|
||||
display_name: None,
|
||||
value: None,
|
||||
name_match_ranges: None,
|
||||
@ -30,12 +35,12 @@ impl Entry {
|
||||
}
|
||||
|
||||
pub fn with_display_name(mut self, display_name: String) -> Self {
|
||||
self.display_name = Some(preprocess_line(&display_name));
|
||||
self.display_name = Some(display_name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_value(mut self, value: String) -> Self {
|
||||
self.value = Some(preprocess_line(&value));
|
||||
self.value = Some(value);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -74,26 +74,26 @@ impl Display for Key {
|
||||
match self {
|
||||
Key::Backspace => write!(f, "Backspace"),
|
||||
Key::Enter => write!(f, "Enter"),
|
||||
Key::Left => write!(f, "←"),
|
||||
Key::Right => write!(f, "→"),
|
||||
Key::Up => write!(f, "↑"),
|
||||
Key::Down => write!(f, "↓"),
|
||||
Key::Left => write!(f, "Left"),
|
||||
Key::Right => write!(f, "Right"),
|
||||
Key::Up => write!(f, "Up"),
|
||||
Key::Down => write!(f, "Down"),
|
||||
Key::CtrlSpace => write!(f, "Ctrl-Space"),
|
||||
Key::CtrlBackspace => write!(f, "Ctrl-Backspace"),
|
||||
Key::CtrlEnter => write!(f, "Ctrl-Enter"),
|
||||
Key::CtrlLeft => write!(f, "Ctrl-←"),
|
||||
Key::CtrlRight => write!(f, "Ctrl-→"),
|
||||
Key::CtrlUp => write!(f, "Ctrl-↑"),
|
||||
Key::CtrlDown => write!(f, "Ctrl-↓"),
|
||||
Key::CtrlLeft => write!(f, "Ctrl-Left"),
|
||||
Key::CtrlRight => write!(f, "Ctrl-Right"),
|
||||
Key::CtrlUp => write!(f, "Ctrl-Up"),
|
||||
Key::CtrlDown => write!(f, "Ctrl-Down"),
|
||||
Key::CtrlDelete => write!(f, "Ctrl-Del"),
|
||||
Key::AltSpace => write!(f, "Alt+Space"),
|
||||
Key::AltEnter => write!(f, "Alt+Enter"),
|
||||
Key::AltBackspace => write!(f, "Alt+Backspace"),
|
||||
Key::AltDelete => write!(f, "Alt+Delete"),
|
||||
Key::AltUp => write!(f, "Alt↑"),
|
||||
Key::AltDown => write!(f, "Alt↓"),
|
||||
Key::AltLeft => write!(f, "Alt←"),
|
||||
Key::AltRight => write!(f, "Alt→"),
|
||||
Key::AltSpace => write!(f, "Alt-Space"),
|
||||
Key::AltEnter => write!(f, "Alt-Enter"),
|
||||
Key::AltBackspace => write!(f, "Alt-Backspace"),
|
||||
Key::AltDelete => write!(f, "Alt-Delete"),
|
||||
Key::AltUp => write!(f, "Alt-Up"),
|
||||
Key::AltDown => write!(f, "Alt-Down"),
|
||||
Key::AltLeft => write!(f, "Alt-Left"),
|
||||
Key::AltRight => write!(f, "Alt-Right"),
|
||||
Key::Home => write!(f, "Home"),
|
||||
Key::End => write!(f, "End"),
|
||||
Key::PageUp => write!(f, "PageUp"),
|
||||
@ -103,7 +103,7 @@ impl Display for Key {
|
||||
Key::Insert => write!(f, "Insert"),
|
||||
Key::F(k) => write!(f, "F{k}"),
|
||||
Key::Char(c) => write!(f, "{c}"),
|
||||
Key::Alt(c) => write!(f, "Alt+{c}"),
|
||||
Key::Alt(c) => write!(f, "Alt-{c}"),
|
||||
Key::Ctrl(c) => write!(f, "Ctrl-{c}"),
|
||||
Key::Null => write!(f, "Null"),
|
||||
Key::Esc => write!(f, "Esc"),
|
||||
|
@ -61,7 +61,7 @@ fn build_tree_preview(entry: &Entry) -> Preview {
|
||||
|
||||
fn label<P: AsRef<Path>>(p: P, strip: &str) -> String {
|
||||
let icon = FileIcon::from(&p);
|
||||
let path = p.as_ref().strip_prefix(strip).unwrap();
|
||||
let path = p.as_ref().strip_prefix(strip).unwrap_or(p.as_ref());
|
||||
format!("{} {}", icon, path.display())
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::channels::remote_control::RemoteControl;
|
||||
use crate::channels::OnAir;
|
||||
use crate::channels::UnitChannel;
|
||||
use crate::channels::{OnAir, UnitChannel};
|
||||
use crate::picker::Picker;
|
||||
use crate::ui::layout::{Dimensions, Layout};
|
||||
use crate::utils::strings::EMPTY_STRING;
|
||||
@ -14,6 +13,7 @@ use crate::{
|
||||
};
|
||||
use crate::{previewers::Previewer, ui::spinner::SpinnerState};
|
||||
use color_eyre::Result;
|
||||
use copypasta::{ClipboardContext, ClipboardProvider};
|
||||
use futures::executor::block_on;
|
||||
use ratatui::{layout::Rect, style::Color, widgets::Paragraph, Frame};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -65,7 +65,7 @@ impl Television {
|
||||
config: Config::default(),
|
||||
channel,
|
||||
remote_control: TelevisionChannel::RemoteControl(
|
||||
RemoteControl::new(),
|
||||
RemoteControl::default(),
|
||||
),
|
||||
mode: Mode::Channel,
|
||||
current_pattern: EMPTY_STRING.to_string(),
|
||||
@ -286,6 +286,9 @@ impl Television {
|
||||
Action::ScrollPreviewHalfPageUp => self.scroll_preview_up(20),
|
||||
Action::ToggleRemoteControl => match self.mode {
|
||||
Mode::Channel => {
|
||||
self.remote_control = TelevisionChannel::RemoteControl(
|
||||
RemoteControl::default(),
|
||||
);
|
||||
self.mode = Mode::RemoteControl;
|
||||
}
|
||||
Mode::RemoteControl => {
|
||||
@ -307,6 +310,7 @@ impl Television {
|
||||
.send(Action::SelectAndExit)?,
|
||||
Mode::RemoteControl => {
|
||||
if let Ok(new_channel) =
|
||||
// FIXME: this is kind of shitty
|
||||
TelevisionChannel::try_from(&entry)
|
||||
{
|
||||
// this resets the RC picker
|
||||
@ -318,24 +322,41 @@ impl Television {
|
||||
}
|
||||
}
|
||||
Mode::SendToChannel => {
|
||||
// if let Ok(new_channel) =
|
||||
// UnitChannel::try_from(&entry)
|
||||
// {
|
||||
// }
|
||||
self.reset_screen();
|
||||
let new_channel = self
|
||||
.channel
|
||||
.transition_to(entry.name.as_str().into());
|
||||
self.reset_picker_selection();
|
||||
self.reset_picker_input();
|
||||
self.remote_control.find(EMPTY_STRING);
|
||||
self.mode = Mode::Channel;
|
||||
// TODO: spawn new channel with selected entries
|
||||
self.change_channel(new_channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::SendToChannel => {
|
||||
self.mode = Mode::SendToChannel;
|
||||
// TODO: build new guide from current channel based on which are pipeable into
|
||||
self.remote_control =
|
||||
TelevisionChannel::RemoteControl(RemoteControl::new());
|
||||
self.reset_screen();
|
||||
}
|
||||
Action::CopyEntryToClipboard => match self.mode {
|
||||
Mode::Channel => {
|
||||
if let Some(entry) = self.get_selected_entry(None) {
|
||||
let mut ctx = ClipboardContext::new().unwrap();
|
||||
ctx.set_contents(entry.name).unwrap();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Action::ToggleSendToChannel => match self.mode {
|
||||
Mode::Channel | Mode::RemoteControl => {
|
||||
self.mode = Mode::SendToChannel;
|
||||
self.remote_control = TelevisionChannel::RemoteControl(
|
||||
RemoteControl::with_transitions_from(&self.channel),
|
||||
);
|
||||
}
|
||||
Mode::SendToChannel => {
|
||||
self.reset_picker_input();
|
||||
self.remote_control.find(EMPTY_STRING);
|
||||
self.reset_picker_selection();
|
||||
self.mode = Mode::Channel;
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Ok(None)
|
||||
@ -395,7 +416,7 @@ impl Television {
|
||||
)?;
|
||||
|
||||
// remote control
|
||||
if matches!(self.mode, Mode::RemoteControl) {
|
||||
if matches!(self.mode, Mode::RemoteControl | Mode::SendToChannel) {
|
||||
self.draw_remote_control(f, &layout.remote_control.unwrap())?;
|
||||
}
|
||||
Ok(())
|
||||
|
@ -23,7 +23,9 @@ impl Television {
|
||||
Mode::RemoteControl => {
|
||||
self.build_keymap_table_for_channel_selection()
|
||||
}
|
||||
Mode::SendToChannel => self.build_keymap_table_for_channel(),
|
||||
Mode::SendToChannel => {
|
||||
self.build_keymap_table_for_channel_transitions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +37,7 @@ impl Television {
|
||||
let prev = keys_for_action(keymap, &Action::SelectPrevEntry);
|
||||
let next = keys_for_action(keymap, &Action::SelectNextEntry);
|
||||
let results_row = Row::new(build_cells_for_key_groups(
|
||||
"↕ Results navigation",
|
||||
"Results navigation",
|
||||
vec![prev, next],
|
||||
key_color,
|
||||
));
|
||||
@ -46,7 +48,7 @@ impl Television {
|
||||
let down_keys =
|
||||
keys_for_action(keymap, &Action::ScrollPreviewHalfPageDown);
|
||||
let preview_row = Row::new(build_cells_for_key_groups(
|
||||
"↕ Preview navigation",
|
||||
"Preview navigation",
|
||||
vec![up_keys, down_keys],
|
||||
key_color,
|
||||
));
|
||||
@ -54,16 +56,25 @@ impl Television {
|
||||
// Select entry
|
||||
let select_entry_keys = keys_for_action(keymap, &Action::SelectEntry);
|
||||
let select_entry_row = Row::new(build_cells_for_key_groups(
|
||||
"✓ Select entry",
|
||||
"Select entry",
|
||||
vec![select_entry_keys],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Copy entry to clipboard
|
||||
let copy_entry_keys =
|
||||
keys_for_action(keymap, &Action::CopyEntryToClipboard);
|
||||
let copy_entry_row = Row::new(build_cells_for_key_groups(
|
||||
"Copy entry to clipboard",
|
||||
vec![copy_entry_keys],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Send to channel
|
||||
let send_to_channel_keys =
|
||||
keys_for_action(keymap, &Action::SendToChannel);
|
||||
keys_for_action(keymap, &Action::ToggleSendToChannel);
|
||||
let send_to_channel_row = Row::new(build_cells_for_key_groups(
|
||||
"⇉ Send results to",
|
||||
"Send results to",
|
||||
vec![send_to_channel_keys],
|
||||
key_color,
|
||||
));
|
||||
@ -72,7 +83,7 @@ impl Television {
|
||||
let switch_channels_keys =
|
||||
keys_for_action(keymap, &Action::ToggleRemoteControl);
|
||||
let switch_channels_row = Row::new(build_cells_for_key_groups(
|
||||
"⨀ Toggle Remote control",
|
||||
"Toggle Remote control",
|
||||
vec![switch_channels_keys],
|
||||
key_color,
|
||||
));
|
||||
@ -81,7 +92,7 @@ impl Television {
|
||||
// Quit ⏼
|
||||
let quit_keys = keys_for_action(keymap, &Action::Quit);
|
||||
let quit_row = Row::new(build_cells_for_key_groups(
|
||||
"⏼ Quit",
|
||||
"Quit",
|
||||
vec![quit_keys],
|
||||
key_color,
|
||||
));
|
||||
@ -93,6 +104,7 @@ impl Television {
|
||||
results_row,
|
||||
preview_row,
|
||||
select_entry_row,
|
||||
copy_entry_row,
|
||||
send_to_channel_row,
|
||||
switch_channels_row,
|
||||
quit_row,
|
||||
@ -111,7 +123,7 @@ impl Television {
|
||||
let prev = keys_for_action(keymap, &Action::SelectPrevEntry);
|
||||
let next = keys_for_action(keymap, &Action::SelectNextEntry);
|
||||
let results_row = Row::new(build_cells_for_key_groups(
|
||||
"↕ Browse channels",
|
||||
"Browse channels",
|
||||
vec![prev, next],
|
||||
key_color,
|
||||
));
|
||||
@ -119,16 +131,16 @@ impl Television {
|
||||
// Select entry
|
||||
let select_entry_keys = keys_for_action(keymap, &Action::SelectEntry);
|
||||
let select_entry_row = Row::new(build_cells_for_key_groups(
|
||||
"✓ Select channel",
|
||||
"Select channel",
|
||||
vec![select_entry_keys],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Switch channels
|
||||
// Remote control
|
||||
let switch_channels_keys =
|
||||
keys_for_action(keymap, &Action::ToggleRemoteControl);
|
||||
let switch_channels_row = Row::new(build_cells_for_key_groups(
|
||||
"⨀ Toggle Remote control",
|
||||
"Toggle Remote control",
|
||||
vec![switch_channels_keys],
|
||||
key_color,
|
||||
));
|
||||
@ -136,7 +148,7 @@ impl Television {
|
||||
// Quit
|
||||
let quit_keys = keys_for_action(keymap, &Action::Quit);
|
||||
let quit_row = Row::new(build_cells_for_key_groups(
|
||||
"⏼ Quit",
|
||||
"Quit",
|
||||
vec![quit_keys],
|
||||
key_color,
|
||||
));
|
||||
@ -147,6 +159,52 @@ impl Television {
|
||||
))
|
||||
}
|
||||
|
||||
fn build_keymap_table_for_channel_transitions<'a>(
|
||||
&self,
|
||||
) -> Result<Table<'a>> {
|
||||
let keymap = self.keymap_for_mode()?;
|
||||
let key_color = mode_color(self.mode);
|
||||
|
||||
// Results navigation
|
||||
let prev = keys_for_action(keymap, &Action::SelectPrevEntry);
|
||||
let next = keys_for_action(keymap, &Action::SelectNextEntry);
|
||||
let results_row = Row::new(build_cells_for_key_groups(
|
||||
"Browse channels",
|
||||
vec![prev, next],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Select entry
|
||||
let select_entry_keys = keys_for_action(keymap, &Action::SelectEntry);
|
||||
let select_entry_row = Row::new(build_cells_for_key_groups(
|
||||
"Send to channel",
|
||||
vec![select_entry_keys],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Cancel
|
||||
let cancel_keys =
|
||||
keys_for_action(keymap, &Action::ToggleSendToChannel);
|
||||
let cancel_row = Row::new(build_cells_for_key_groups(
|
||||
"Cancel",
|
||||
vec![cancel_keys],
|
||||
key_color,
|
||||
));
|
||||
|
||||
// Quit
|
||||
let quit_keys = keys_for_action(keymap, &Action::Quit);
|
||||
let quit_row = Row::new(build_cells_for_key_groups(
|
||||
"Quit",
|
||||
vec![quit_keys],
|
||||
key_color,
|
||||
));
|
||||
|
||||
Ok(Table::new(
|
||||
vec![results_row, select_entry_row, cancel_row, quit_row],
|
||||
vec![Constraint::Fill(1), Constraint::Fill(2)],
|
||||
))
|
||||
}
|
||||
|
||||
/// Get the keymap for the current mode.
|
||||
///
|
||||
/// # Returns
|
||||
|
@ -1,9 +1,9 @@
|
||||
use crate::television::Mode;
|
||||
use ratatui::style::Color;
|
||||
|
||||
const CHANNEL_COLOR: Color = Color::LightYellow;
|
||||
const REMOTE_CONTROL_COLOR: Color = Color::LightMagenta;
|
||||
const SEND_TO_CHANNEL_COLOR: Color = Color::LightCyan;
|
||||
const CHANNEL_COLOR: Color = Color::Indexed(222);
|
||||
const REMOTE_CONTROL_COLOR: Color = Color::Indexed(1);
|
||||
const SEND_TO_CHANNEL_COLOR: Color = Color::Indexed(105);
|
||||
|
||||
pub fn mode_color(mode: Mode) -> Color {
|
||||
match mode {
|
||||
|
@ -24,11 +24,11 @@ impl Television {
|
||||
.direction(Direction::Vertical)
|
||||
.constraints(
|
||||
[
|
||||
Constraint::Fill(1),
|
||||
Constraint::Min(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(20),
|
||||
]
|
||||
.as_ref(),
|
||||
.as_ref(),
|
||||
)
|
||||
.split(*area);
|
||||
self.draw_rc_channels(f, &layout[0])?;
|
||||
@ -106,7 +106,7 @@ impl Television {
|
||||
.fg(crate::television::DEFAULT_INPUT_FG)
|
||||
.bold(),
|
||||
))
|
||||
.block(prompt_symbol_block);
|
||||
.block(prompt_symbol_block);
|
||||
f.render_widget(arrow, inner_input_chunks[0]);
|
||||
|
||||
let interactive_input_block = Block::default();
|
||||
@ -131,8 +131,8 @@ impl Television {
|
||||
// Put cursor past the end of the input text
|
||||
inner_input_chunks[1].x
|
||||
+ u16::try_from(
|
||||
self.rc_picker.input.visual_cursor().max(scroll) - scroll,
|
||||
)?,
|
||||
self.rc_picker.input.visual_cursor().max(scroll) - scroll,
|
||||
)?,
|
||||
// Move one line down, from the border to the input line
|
||||
inner_input_chunks[1].y,
|
||||
));
|
||||
|
@ -1,10 +1,10 @@
|
||||
use lazy_static::lazy_static;
|
||||
use std::fmt::Write;
|
||||
use tracing::debug;
|
||||
|
||||
pub fn next_char_boundary(s: &str, start: usize) -> usize {
|
||||
let mut i = start;
|
||||
while !s.is_char_boundary(i) {
|
||||
let len = s.len();
|
||||
while !s.is_char_boundary(i) && i < len - 1 {
|
||||
i += 1;
|
||||
}
|
||||
i
|
||||
@ -12,7 +12,7 @@ pub fn next_char_boundary(s: &str, start: usize) -> usize {
|
||||
|
||||
pub fn prev_char_boundary(s: &str, start: usize) -> usize {
|
||||
let mut i = start;
|
||||
while !s.is_char_boundary(i) {
|
||||
while !s.is_char_boundary(i) && i > 0 {
|
||||
i -= 1;
|
||||
}
|
||||
i
|
||||
@ -23,6 +23,9 @@ pub fn slice_at_char_boundaries(
|
||||
start_byte_index: usize,
|
||||
end_byte_index: usize,
|
||||
) -> &str {
|
||||
if start_byte_index > end_byte_index || start_byte_index > s.len() || end_byte_index > s.len() {
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
&s[prev_char_boundary(s, start_byte_index)
|
||||
..next_char_boundary(s, end_byte_index)]
|
||||
}
|
||||
@ -97,10 +100,7 @@ pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String {
|
||||
output.push(*NULL_SYMBOL);
|
||||
}
|
||||
// everything else
|
||||
c => {
|
||||
debug!("char: {:?}", c);
|
||||
output.push(c)
|
||||
}
|
||||
c => output.push(c),
|
||||
}
|
||||
} else {
|
||||
write!(output, "\\x{:02X}", input[idx]).ok();
|
||||
@ -138,8 +138,8 @@ pub fn preprocess_line(line: &str) -> String {
|
||||
line
|
||||
}
|
||||
}
|
||||
.trim_end_matches(['\r', '\n', '\0'])
|
||||
.as_bytes(),
|
||||
.trim_end_matches(['\r', '\n', '\0'])
|
||||
.as_bytes(),
|
||||
TAB_WIDTH,
|
||||
)
|
||||
}
|
||||
|
@ -6,10 +6,10 @@ use quote::quote;
|
||||
///
|
||||
/// ```rust
|
||||
/// use crate::channels::{TelevisionChannel, OnAir};
|
||||
/// use television_derive::CliChannel;
|
||||
/// use television_derive::ToCliChannel;
|
||||
/// use crate::channels::{files, text};
|
||||
///
|
||||
/// #[derive(CliChannel)]
|
||||
/// #[derive(ToCliChannel)]
|
||||
/// enum TelevisionChannel {
|
||||
/// Files(files::Channel),
|
||||
/// Text(text::Channel),
|
||||
@ -25,7 +25,7 @@ use quote::quote;
|
||||
///
|
||||
/// Any variant that should not be included in the CLI should be annotated with
|
||||
/// `#[exclude_from_cli]`.
|
||||
#[proc_macro_derive(CliChannel, attributes(exclude_from_cli))]
|
||||
#[proc_macro_derive(ToCliChannel, attributes(exclude_from_cli))]
|
||||
pub fn cli_channel_derive(input: TokenStream) -> TokenStream {
|
||||
// Construct a representation of Rust code as a syntax tree
|
||||
// that we can manipulate
|
||||
@ -35,12 +35,12 @@ pub fn cli_channel_derive(input: TokenStream) -> TokenStream {
|
||||
impl_cli_channel(&ast)
|
||||
}
|
||||
|
||||
fn has_exclude_attr(attrs: &[syn::Attribute]) -> bool {
|
||||
attrs
|
||||
.iter()
|
||||
.any(|attr| attr.path().is_ident("exclude_from_cli"))
|
||||
fn has_attribute(attrs: &[syn::Attribute], attribute: &str) -> bool {
|
||||
attrs.iter().any(|attr| attr.path().is_ident(attribute))
|
||||
}
|
||||
|
||||
const EXCLUDE_FROM_CLI: &str = "exclude_from_cli";
|
||||
|
||||
fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
|
||||
// check that the struct is an enum
|
||||
let variants = if let syn::Data::Enum(data_enum) = &ast.data {
|
||||
@ -58,7 +58,7 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
|
||||
// create the CliTvChannel enum
|
||||
let cli_enum_variants = variants
|
||||
.iter()
|
||||
.filter(|variant| !has_exclude_attr(&variant.attrs))
|
||||
.filter(|variant| !has_attribute(&variant.attrs, EXCLUDE_FROM_CLI))
|
||||
.map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
quote! {
|
||||
@ -80,7 +80,7 @@ fn impl_cli_channel(ast: &syn::DeriveInput) -> TokenStream {
|
||||
|
||||
// Generate the match arms for the `to_channel` method
|
||||
let arms = variants.iter().filter(
|
||||
|variant| !has_exclude_attr(&variant.attrs)
|
||||
|variant| !has_attribute(&variant.attrs, EXCLUDE_FROM_CLI),
|
||||
).map(|variant| {
|
||||
let variant_name = &variant.ident;
|
||||
|
||||
@ -255,7 +255,7 @@ fn impl_tv_channel(ast: &syn::DeriveInput) -> TokenStream {
|
||||
///
|
||||
/// The `UnitChannel` enum is used as a unit variant of the `TelevisionChannel`
|
||||
/// enum.
|
||||
#[proc_macro_derive(UnitChannel)]
|
||||
#[proc_macro_derive(ToUnitChannel, attributes(exclude_from_unit))]
|
||||
pub fn unit_channel_derive(input: TokenStream) -> TokenStream {
|
||||
// Construct a representation of Rust code as a syntax tree
|
||||
// that we can manipulate
|
||||
@ -265,6 +265,8 @@ pub fn unit_channel_derive(input: TokenStream) -> TokenStream {
|
||||
impl_unit_channel(&ast)
|
||||
}
|
||||
|
||||
const EXCLUDE_FROM_UNIT: &str = "exclude_from_unit";
|
||||
|
||||
fn impl_unit_channel(ast: &syn::DeriveInput) -> TokenStream {
|
||||
// Ensure the struct is an enum
|
||||
let variants = if let syn::Data::Enum(data_enum) = &ast.data {
|
||||
@ -279,7 +281,17 @@ fn impl_unit_channel(ast: &syn::DeriveInput) -> TokenStream {
|
||||
"#[derive(UnitChannel)] requires at least one variant"
|
||||
);
|
||||
|
||||
let variant_names: Vec<_> = variants.iter().map(|v| &v.ident).collect();
|
||||
let variant_names: Vec<_> = variants
|
||||
.iter()
|
||||
.filter(|variant| !has_attribute(&variant.attrs, EXCLUDE_FROM_UNIT))
|
||||
.map(|v| &v.ident)
|
||||
.collect();
|
||||
|
||||
let excluded_variants: Vec<_> = variants
|
||||
.iter()
|
||||
.filter(|variant| has_attribute(&variant.attrs, EXCLUDE_FROM_UNIT))
|
||||
.map(|v| &v.ident)
|
||||
.collect();
|
||||
|
||||
// Generate a unit enum from the given enum
|
||||
let unit_enum = quote! {
|
||||
@ -310,7 +322,37 @@ fn impl_unit_channel(ast: &syn::DeriveInput) -> TokenStream {
|
||||
fn from(channel: &TelevisionChannel) -> Self {
|
||||
match channel {
|
||||
#(
|
||||
TelevisionChannel::#variant_names(_) => UnitChannel::#variant_names,
|
||||
TelevisionChannel::#variant_names(_) => Self::#variant_names,
|
||||
)*
|
||||
#(
|
||||
TelevisionChannel::#excluded_variants(_) => panic!("Cannot convert excluded variant to unit channel."),
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Generate From<&str> implementation
|
||||
let from_str_impl = quote! {
|
||||
impl From<&str> for UnitChannel {
|
||||
fn from(channel: &str) -> Self {
|
||||
match channel {
|
||||
#(
|
||||
stringify!(#variant_names) => Self::#variant_names,
|
||||
)*
|
||||
_ => panic!("Invalid unit channel name."),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Generate Into<&str> implementation
|
||||
let into_str_impl = quote! {
|
||||
impl Into<&str> for UnitChannel {
|
||||
fn into(self) -> &'static str {
|
||||
match self {
|
||||
#(
|
||||
UnitChannel::#variant_names => stringify!(#variant_names),
|
||||
)*
|
||||
}
|
||||
}
|
||||
@ -321,6 +363,8 @@ fn impl_unit_channel(ast: &syn::DeriveInput) -> TokenStream {
|
||||
#unit_enum
|
||||
#into_impl
|
||||
#from_impl
|
||||
#from_str_impl
|
||||
#into_str_impl
|
||||
};
|
||||
|
||||
gen.into()
|
||||
|
Loading…
x
Reference in New Issue
Block a user