Merging with origin

This commit is contained in:
azy 2025-03-05 15:56:53 +09:00
commit 32c4c042e7
26 changed files with 914 additions and 991 deletions

473
Cargo.lock generated
View File

@ -154,9 +154,9 @@ dependencies = [
[[package]]
name = "avif-serialize"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62"
checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e"
dependencies = [
"arrayvec",
]
@ -173,7 +173,7 @@ dependencies = [
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets 0.52.6",
"windows-targets",
]
[[package]]
@ -265,12 +265,6 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
[[package]]
name = "block"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "bstr"
version = "1.11.3"
@ -283,9 +277,9 @@ dependencies = [
[[package]]
name = "built"
version = "0.7.6"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73848a43c5d63a1251d17adf6c2bf78aa94830e60a335a95eeea45d6ba9e1e4d"
checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b"
[[package]]
name = "bumpalo"
@ -323,32 +317,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc"
[[package]]
name = "calloop"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
dependencies = [
"bitflags 2.8.0",
"log",
"polling",
"rustix",
"slab",
"thiserror 1.0.69",
]
[[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 = "cassowary"
version = "0.3.0"
@ -466,12 +434,11 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "clipboard-win"
version = "3.1.1"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342"
checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
dependencies = [
"lazy-bytes-cast",
"winapi",
"error-code",
]
[[package]]
@ -511,15 +478,6 @@ 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 = "console"
version = "0.15.10"
@ -542,20 +500,6 @@ dependencies = [
"memchr",
]
[[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 = "crc32fast"
version = "1.4.2"
@ -661,12 +605,6 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
[[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"
@ -741,21 +679,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "dlib"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
"libloading",
]
[[package]]
name = "downcast-rs"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]]
name = "either"
version = "1.13.0"
@ -793,6 +716,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "error-code"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
[[package]]
name = "exr"
version = "1.73.0"
@ -927,16 +856,6 @@ dependencies = [
"tempfile",
]
[[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"
@ -957,7 +876,7 @@ dependencies = [
"cfg-if",
"libc",
"wasi 0.13.3+wasi-0.2.2",
"windows-targets 0.52.6",
"windows-targets",
]
[[package]]
@ -1224,12 +1143,6 @@ dependencies = [
"wasm-bindgen",
]
[[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"
@ -1258,16 +1171,6 @@ dependencies = [
"cc",
]
[[package]]
name = "libloading"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
]
[[package]]
name = "libredox"
version = "0.1.3"
@ -1318,15 +1221,6 @@ dependencies = [
"hashbrown",
]
[[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"
@ -1352,15 +1246,6 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memmap2"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
dependencies = [
"libc",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -1507,35 +1392,6 @@ dependencies = [
"autocfg",
]
[[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.36.7"
@ -1622,7 +1478,7 @@ dependencies = [
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.6",
"windows-targets",
]
[[package]]
@ -1666,7 +1522,7 @@ checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
dependencies = [
"base64",
"indexmap",
"quick-xml 0.32.0",
"quick-xml",
"serde",
"time",
]
@ -1712,21 +1568,6 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "polling"
version = "3.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi",
"pin-project-lite",
"rustix",
"tracing",
"windows-sys 0.59.0",
]
[[package]]
name = "powerfmt"
version = "0.2.0"
@ -1794,15 +1635,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "quick-xml"
version = "0.37.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.38"
@ -2053,12 +1885,6 @@ 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"
@ -2214,57 +2040,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
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.8.0",
"calloop",
"calloop-wayland-source",
"cursor-icon",
"libc",
"log",
"memmap2",
"rustix",
"thiserror 1.0.69",
"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.8"
@ -2370,10 +2151,11 @@ name = "television"
version = "0.10.6"
dependencies = [
"anyhow",
"base64",
"bat",
"better-panic",
"clap",
"copypasta",
"clipboard-win",
"criterion",
"crossterm",
"devicons",
@ -2382,7 +2164,6 @@ dependencies = [
"human-panic",
"ignore",
"image",
"lazy_static",
"nom",
"nucleo",
"parking_lot",
@ -2844,102 +2625,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "wayland-backend"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf"
dependencies = [
"cc",
"downcast-rs",
"rustix",
"scoped-tls",
"smallvec",
"wayland-sys",
]
[[package]]
name = "wayland-client"
version = "0.31.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f"
dependencies = [
"bitflags 2.8.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.8.0",
"cursor-icon",
"wayland-backend",
]
[[package]]
name = "wayland-cursor"
version = "0.31.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a93029cbb6650748881a00e4922b076092a6a08c11e7fbdb923f064b23968c5d"
dependencies = [
"rustix",
"wayland-client",
"xcursor",
]
[[package]]
name = "wayland-protocols"
version = "0.32.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc"
dependencies = [
"bitflags 2.8.0",
"wayland-backend",
"wayland-client",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols-wlr"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2"
dependencies = [
"bitflags 2.8.0",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-scanner",
]
[[package]]
name = "wayland-scanner"
version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484"
dependencies = [
"proc-macro2",
"quick-xml 0.37.2",
"quote",
]
[[package]]
name = "wayland-sys"
version = "0.31.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615"
dependencies = [
"dlib",
"log",
"once_cell",
"pkg-config",
]
[[package]]
name = "web-sys"
version = "0.3.77"
@ -2994,7 +2679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132"
dependencies = [
"windows-core",
"windows-targets 0.52.6",
"windows-targets",
]
[[package]]
@ -3006,7 +2691,7 @@ dependencies = [
"windows-implement",
"windows-interface",
"windows-result",
"windows-targets 0.52.6",
"windows-targets",
]
[[package]]
@ -3037,7 +2722,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
dependencies = [
"windows-targets 0.52.6",
"windows-targets",
]
[[package]]
@ -3046,7 +2731,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
"windows-targets",
]
[[package]]
@ -3055,22 +2740,7 @@ version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
"windows-targets",
]
[[package]]
@ -3079,46 +2749,28 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
@ -3131,48 +2783,24 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
@ -3197,45 +2825,6 @@ dependencies = [
"bitflags 2.8.0",
]
[[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 = "xterm-color"
version = "1.0.1"

View File

@ -31,9 +31,9 @@ path = "television/lib.rs"
television-derive = { path = "television-derive", version = "0.0.25" }
anyhow = "1.0"
base64 = "0.22.1"
directories = "6.0"
devicons = "0.6"
lazy_static = "1.5"
tokio = { version = "1.43", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
@ -46,7 +46,6 @@ ratatui = { version = "0.29", features = ["serde", "macros"] }
better-panic = "0.3"
signal-hook = "0.3"
human-panic = "2.0"
copypasta = "0.10"
ignore = "0.4"
strum = { version = "0.26", features = ["derive"] }
regex = "1.11"
@ -64,6 +63,7 @@ image = "0.25"
[target.'cfg(windows)'.dependencies]
winapi-util = "0.1.9"
clipboard-win = "5.4.0"
[dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio"] }

View File

@ -33,7 +33,7 @@
pkgs.rustChannelOf
{
rustToolchain = ./rust-toolchain.toml;
sha256 = "VZZnlyP69+Y3crrLHQyJirqlHrTtGTsyiSnZB8jEvVo=";
sha256 = "s1RPtyvDGJaX/BisLT+ifVfuhDT1nZkZ1NcK8sbwELM=";
}
)
.rust;

View File

@ -8,6 +8,7 @@ use crate::channels::entry::Entry;
use crate::channels::TelevisionChannel;
use crate::config::{parse_key, Config};
use crate::keymap::Keymap;
use crate::render::UiState;
use crate::television::{Mode, Television};
use crate::{
action::Action,
@ -37,6 +38,9 @@ pub struct App {
event_abort_tx: mpsc::UnboundedSender<()>,
/// A sender channel for rendering tasks.
render_tx: mpsc::UnboundedSender<RenderingTask>,
/// A channel that listens to UI updates.
ui_state_rx: mpsc::UnboundedReceiver<UiState>,
ui_state_tx: mpsc::UnboundedSender<UiState>,
}
/// The outcome of an action.
@ -104,6 +108,7 @@ impl App {
.collect(),
)?;
debug!("{:?}", keymap);
let (ui_state_tx, ui_state_rx) = mpsc::unbounded_channel();
let television =
Television::new(action_tx.clone(), channel, config, input);
@ -118,6 +123,8 @@ impl App {
event_rx,
event_abort_tx,
render_tx,
ui_state_rx,
ui_state_tx,
})
}
@ -145,9 +152,10 @@ impl App {
debug!("Starting rendering loop");
let (render_tx, render_rx) = mpsc::unbounded_channel();
self.render_tx = render_tx.clone();
let ui_state_tx = self.ui_state_tx.clone();
let action_tx_r = self.action_tx.clone();
let rendering_task = tokio::spawn(async move {
render(render_rx, action_tx_r, is_output_tty).await
render(render_rx, action_tx_r, ui_state_tx, is_output_tty).await
});
self.action_tx.send(Action::Render)?;
@ -298,9 +306,14 @@ impl App {
self.render_tx.send(RenderingTask::Resize(w, h))?;
}
Action::Render => {
// forward to the rendering task
self.render_tx.send(RenderingTask::Render(
self.television.dump_context(),
Box::new(self.television.dump_context()),
))?;
// update the television UI state with the previous frame
if let Ok(ui_state) = self.ui_state_rx.try_recv() {
self.television.update_ui_state(ui_state);
}
}
_ => {}
}

View File

@ -9,7 +9,6 @@ use std::io::{BufRead, BufReader};
use std::process::Stdio;
use anyhow::Result;
use lazy_static::lazy_static;
use regex::Regex;
use rustc_hash::{FxBuildHasher, FxHashSet};
use tracing::debug;
@ -64,13 +63,10 @@ impl From<CableChannelPrototype> for Channel {
}
}
lazy_static! {
static ref BUILTIN_PREVIEW_RE: Regex = Regex::new(r"^:(\w+):$").unwrap();
}
fn parse_preview_kind(command: &PreviewCommand) -> Result<PreviewKind> {
debug!("Parsing preview kind for command: {:?}", command);
if let Some(captures) = BUILTIN_PREVIEW_RE.captures(&command.command) {
let re = Regex::new(r"^\:(\w+)\:$").unwrap();
if let Some(captures) = re.captures(&command.command) {
let preview_type = PreviewType::try_from(&captures[1])?;
Ok(PreviewKind::Builtin(preview_type))
} else {

View File

@ -1,7 +1,7 @@
use crate::channels::entry::{Entry, PreviewCommand, PreviewType};
use crate::channels::{OnAir, TelevisionChannel};
use crate::matcher::{config::Config, injector::Injector, Matcher};
use crate::utils::files::{walk_builder, DEFAULT_NUM_THREADS};
use crate::utils::files::{get_default_num_threads, walk_builder};
use devicons::FileIcon;
use rustc_hash::{FxBuildHasher, FxHashSet};
use std::collections::HashSet;
@ -151,7 +151,7 @@ async fn load_dirs(paths: Vec<PathBuf>, injector: Injector<String>) {
}
let current_dir = std::env::current_dir().unwrap();
let mut builder =
walk_builder(&paths[0], *DEFAULT_NUM_THREADS, None, None);
walk_builder(&paths[0], get_default_num_threads(), None, None);
paths[1..].iter().for_each(|path| {
builder.add(path);
});

View File

@ -1,7 +1,7 @@
use crate::channels::entry::{Entry, PreviewType};
use crate::channels::{OnAir, TelevisionChannel};
use crate::matcher::{config::Config, injector::Injector, Matcher};
use crate::utils::files::{walk_builder, DEFAULT_NUM_THREADS};
use crate::utils::files::{get_default_num_threads, walk_builder};
use devicons::FileIcon;
use rustc_hash::{FxBuildHasher, FxHashSet};
use std::collections::HashSet;
@ -157,7 +157,7 @@ async fn load_files(paths: Vec<PathBuf>, injector: Injector<String>) {
}
let current_dir = std::env::current_dir().unwrap();
let mut builder =
walk_builder(&paths[0], *DEFAULT_NUM_THREADS, None, None);
walk_builder(&paths[0], get_default_num_threads(), None, None);
paths[1..].iter().for_each(|path| {
builder.add(path);
});

View File

@ -1,7 +1,6 @@
use devicons::FileIcon;
use directories::BaseDirs;
use ignore::overrides::OverrideBuilder;
use lazy_static::lazy_static;
use rustc_hash::{FxBuildHasher, FxHashSet};
use std::collections::HashSet;
use std::path::PathBuf;
@ -11,13 +10,14 @@ use tracing::debug;
use crate::channels::entry::{Entry, PreviewCommand, PreviewType};
use crate::channels::OnAir;
use crate::matcher::{config::Config, injector::Injector, Matcher};
use crate::utils::files::{walk_builder, DEFAULT_NUM_THREADS};
use crate::utils::files::{get_default_num_threads, walk_builder};
pub struct Channel {
matcher: Matcher<String>,
icon: FileIcon,
crawl_handle: JoinHandle<()>,
selected_entries: FxHashSet<Entry>,
preview_command: PreviewCommand,
}
impl Channel {
@ -28,11 +28,20 @@ impl Channel {
base_dirs.home_dir().to_path_buf(),
matcher.injector(),
));
let preview_command = PreviewCommand {
command: String::from(
"cd {} && git log -n 200 --pretty=medium --all --graph --color",
),
delimiter: ":".to_string(),
};
Channel {
matcher,
icon: FileIcon::from("git"),
crawl_handle,
selected_entries: HashSet::with_hasher(FxBuildHasher),
preview_command,
}
}
}
@ -43,15 +52,6 @@ impl Default for Channel {
}
}
lazy_static! {
static ref PREVIEW_COMMAND: PreviewCommand = PreviewCommand {
command: String::from(
"cd {} && git log -n 200 --pretty=medium --all --graph --color",
),
delimiter: ":".to_string(),
};
}
impl OnAir for Channel {
fn find(&mut self, pattern: &str) {
self.matcher.find(pattern);
@ -64,9 +64,12 @@ impl OnAir for Channel {
.into_iter()
.map(|item| {
let path = item.matched_string;
Entry::new(path, PreviewType::Command(PREVIEW_COMMAND.clone()))
.with_name_match_ranges(&item.match_indices)
.with_icon(self.icon)
Entry::new(
path,
PreviewType::Command(self.preview_command.clone()),
)
.with_name_match_ranges(&item.match_indices)
.with_icon(self.icon)
})
.collect()
}
@ -74,8 +77,11 @@ impl OnAir for Channel {
fn get_result(&self, index: u32) -> Option<Entry> {
self.matcher.get_result(index).map(|item| {
let path = item.matched_string;
Entry::new(path, PreviewType::Command(PREVIEW_COMMAND.clone()))
.with_icon(self.icon)
Entry::new(
path,
PreviewType::Command(self.preview_command.clone()),
)
.with_icon(self.icon)
})
}
@ -162,7 +168,7 @@ async fn crawl_for_repos(starting_point: PathBuf, injector: Injector<String>) {
walker_overrides_builder.add(".git").unwrap();
let walker = walk_builder(
&starting_point,
*DEFAULT_NUM_THREADS,
get_default_num_threads(),
Some(walker_overrides_builder.build().unwrap()),
Some(get_ignored_paths()),
)

View File

@ -1,7 +1,7 @@
use super::{OnAir, TelevisionChannel};
use crate::channels::entry::{Entry, PreviewType};
use crate::matcher::{config::Config, injector::Injector, Matcher};
use crate::utils::files::{walk_builder, DEFAULT_NUM_THREADS};
use crate::utils::files::{get_default_num_threads, walk_builder};
use crate::utils::strings::{
proportion_of_printable_ascii_characters, PRINTABLE_ASCII_THRESHOLD,
};
@ -277,7 +277,7 @@ async fn crawl_for_candidates(
}
let current_dir = std::env::current_dir().unwrap();
let mut walker =
walk_builder(&directories[0], *DEFAULT_NUM_THREADS, None, None);
walk_builder(&directories[0], get_default_num_threads(), None, None);
directories[1..].iter().for_each(|path| {
walker.add(path);
});

View File

@ -9,7 +9,6 @@ use anyhow::{Context, Result};
use directories::ProjectDirs;
use keybindings::merge_keybindings;
pub use keybindings::{parse_key, Binding, KeyBindings};
use lazy_static::lazy_static;
use previewers::PreviewersConfig;
use serde::Deserialize;
use shell_integration::ShellIntegrationConfig;
@ -70,23 +69,7 @@ pub struct Config {
pub shell_integration: ShellIntegrationConfig,
}
lazy_static! {
pub static ref PROJECT_NAME: String = String::from("television");
pub static ref PROJECT_NAME_UPPER: String = PROJECT_NAME.to_uppercase();
pub static ref DATA_FOLDER: Option<PathBuf> =
// if `TELEVISION_DATA` is set, use that as the data directory
env::var_os(format!("{}_DATA", PROJECT_NAME_UPPER.clone())).map(PathBuf::from).or_else(|| {
// otherwise, use the XDG data directory
env::var_os("XDG_DATA_HOME").map(PathBuf::from).map(|p| p.join(PROJECT_NAME.as_str())).filter(|p| p.is_absolute())
});
pub static ref CONFIG_FOLDER: Option<PathBuf> =
// if `TELEVISION_CONFIG` is set, use that as the television config directory
env::var_os(format!("{}_CONFIG", PROJECT_NAME_UPPER.clone())).map(PathBuf::from).or_else(|| {
// otherwise, use the XDG config directory + 'television'
env::var_os("XDG_CONFIG_HOME").map(PathBuf::from).map(|p| p.join(PROJECT_NAME.as_str())).filter(|p| p.is_absolute())
});
}
const PROJECT_NAME: &str = "television";
const CONFIG_FILE_NAME: &str = "config.toml";
pub struct ConfigEnv {
@ -184,7 +167,19 @@ impl Config {
}
pub fn get_data_dir() -> PathBuf {
let directory = if let Some(s) = DATA_FOLDER.clone() {
// if `TELEVISION_DATA` is set, use that as the data directory
let data_folder =
env::var_os(format!("{}_DATA", PROJECT_NAME.to_uppercase()))
.map(PathBuf::from)
.or_else(|| {
// otherwise, use the XDG data directory
env::var_os("XDG_DATA_HOME")
.map(PathBuf::from)
.map(|p| p.join(PROJECT_NAME))
.filter(|p| p.is_absolute())
});
let directory = if let Some(s) = data_folder {
debug!("Using data directory: {:?}", s);
s
} else if let Some(proj_dirs) = project_directory() {
@ -197,7 +192,18 @@ pub fn get_data_dir() -> PathBuf {
}
pub fn get_config_dir() -> PathBuf {
let directory = if let Some(s) = CONFIG_FOLDER.clone() {
// if `TELEVISION_CONFIG` is set, use that as the television config directory
let config_dir =
env::var_os(format!("{}_CONFIG", PROJECT_NAME.to_uppercase()))
.map(PathBuf::from)
.or_else(|| {
// otherwise, use the XDG config directory + 'television'
env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.map(|p| p.join(PROJECT_NAME))
.filter(|p| p.is_absolute())
});
let directory = if let Some(s) = config_dir {
debug!("Config directory: {:?}", s);
s
} else if cfg!(unix) {

View File

@ -128,10 +128,10 @@ impl Theme {
pub fn from_builtin(
name: &str,
) -> Result<Self, Box<dyn std::error::Error>> {
let theme_content: &str = builtin::BUILTIN_THEMES.get(name).map_or(
builtin::BUILTIN_THEMES.get(DEFAULT_THEME).unwrap(),
|t| *t,
);
let builtin_themes = builtin::builtin_themes();
let theme_content: &str = builtin_themes
.get(name)
.map_or(builtin_themes.get(DEFAULT_THEME).unwrap(), |t| *t);
let theme = toml::from_str(theme_content)?;
Ok(theme)
}

View File

@ -1,43 +1,39 @@
use rustc_hash::FxHashMap;
use lazy_static::lazy_static;
lazy_static! {
pub static ref BUILTIN_THEMES: FxHashMap<&'static str, &'static str> = {
let mut m = FxHashMap::default();
m.insert("default", include_str!("../../../themes/default.toml"));
m.insert(
"television",
include_str!("../../../themes/television.toml"),
);
m.insert(
"gruvbox-dark",
include_str!("../../../themes/gruvbox-dark.toml"),
);
m.insert(
"gruvbox-light",
include_str!("../../../themes/gruvbox-light.toml"),
);
m.insert(
"catppuccin",
include_str!("../../../themes/catppuccin.toml"),
);
m.insert("nord-dark", include_str!("../../../themes/nord-dark.toml"));
m.insert(
"solarized-dark",
include_str!("../../../themes/solarized-dark.toml"),
);
m.insert(
"solarized-light",
include_str!("../../../themes/solarized-light.toml"),
);
m.insert("dracula", include_str!("../../../themes/dracula.toml"));
m.insert("monokai", include_str!("../../../themes/monokai.toml"));
m.insert("onedark", include_str!("../../../themes/onedark.toml"));
m.insert(
"tokyonight",
include_str!("../../../themes/tokyonight.toml"),
);
m
};
pub fn builtin_themes() -> FxHashMap<&'static str, &'static str> {
let mut m = FxHashMap::default();
m.insert("default", include_str!("../../../themes/default.toml"));
m.insert(
"television",
include_str!("../../../themes/television.toml"),
);
m.insert(
"gruvbox-dark",
include_str!("../../../themes/gruvbox-dark.toml"),
);
m.insert(
"gruvbox-light",
include_str!("../../../themes/gruvbox-light.toml"),
);
m.insert(
"catppuccin",
include_str!("../../../themes/catppuccin.toml"),
);
m.insert("nord-dark", include_str!("../../../themes/nord-dark.toml"));
m.insert(
"solarized-dark",
include_str!("../../../themes/solarized-dark.toml"),
);
m.insert(
"solarized-light",
include_str!("../../../themes/solarized-light.toml"),
);
m.insert("dracula", include_str!("../../../themes/dracula.toml"));
m.insert("monokai", include_str!("../../../themes/monokai.toml"));
m.insert("onedark", include_str!("../../../themes/onedark.toml"));
m.insert(
"tokyonight",
include_str!("../../../themes/tokyonight.toml"),
);
m
}

View File

@ -3,7 +3,6 @@ use std::{hash::Hash, time::Instant};
use anyhow::Result;
use ratatui::{layout::Rect, Frame};
use rustc_hash::FxHashSet;
use tokio::sync::mpsc::Sender;
use crate::{
action::Action,
@ -18,7 +17,7 @@ use crate::{
remote_control::draw_remote_control, results::draw_results_list,
spinner::Spinner,
},
television::{Message, Mode},
television::Mode,
utils::metadata::AppMetadata,
};
@ -61,7 +60,6 @@ impl Hash for ChannelState {
pub struct TvState {
pub mode: Mode,
pub selected_entry: Option<Entry>,
pub results_area_height: u16,
pub results_picker: Picker,
pub rc_picker: Picker,
pub channel_state: ChannelState,
@ -74,7 +72,6 @@ impl TvState {
pub fn new(
mode: Mode,
selected_entry: Option<Entry>,
results_area_height: u16,
results_picker: Picker,
rc_picker: Picker,
channel_state: ChannelState,
@ -84,7 +81,6 @@ impl TvState {
Self {
mode,
selected_entry,
results_area_height,
results_picker,
rc_picker,
channel_state,
@ -100,8 +96,8 @@ pub struct Ctx {
pub config: Config,
pub colorscheme: Colorscheme,
pub app_metadata: AppMetadata,
pub tv_tx_handle: Sender<Message>,
pub instant: Instant,
pub layout: Layout,
}
impl Ctx {
@ -110,16 +106,16 @@ impl Ctx {
config: Config,
colorscheme: Colorscheme,
app_metadata: AppMetadata,
tv_tx_handle: Sender<Message>,
instant: Instant,
layout: Layout,
) -> Self {
Self {
tv_state,
config,
colorscheme,
app_metadata,
tv_tx_handle,
instant,
layout,
}
}
}
@ -156,7 +152,7 @@ impl Ord for Ctx {
}
}
pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<()> {
pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<Layout> {
let selected_entry = ctx
.tv_state
.selected_entry
@ -185,14 +181,6 @@ pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<()> {
&ctx.colorscheme,
);
if layout.results.height.saturating_sub(2)
!= ctx.tv_state.results_area_height
{
ctx.tv_tx_handle.try_send(Message::ResultListHeightChanged(
layout.results.height.saturating_sub(2),
))?;
}
// results list
draw_results_list(
f,
@ -259,5 +247,5 @@ pub fn draw(ctx: &Ctx, f: &mut Frame<'_>, area: Rect) -> Result<()> {
)?;
}
Ok(())
Ok(layout)
}

View File

@ -3,14 +3,10 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter};
use crate::config::get_data_dir;
lazy_static::lazy_static! {
pub static ref LOG_FILE: String = format!("{}.log", env!("CARGO_PKG_NAME"));
}
pub fn init() -> Result<()> {
let directory = get_data_dir();
std::fs::create_dir_all(directory.clone())?;
let log_path = directory.join(LOG_FILE.clone());
let log_path = directory.join(format!("{}.log", env!("CARGO_PKG_NAME")));
let log_file = std::fs::File::create(log_path)?;
let file_subscriber = fmt::layer()
.with_file(true)

View File

@ -5,6 +5,7 @@ use std::process::exit;
use anyhow::Result;
use clap::Parser;
use television::utils::clipboard::CLIPBOARD;
use tracing::{debug, error, info};
use television::app::App;
@ -77,6 +78,8 @@ async fn main() -> Result<()> {
env::set_current_dir(path)?;
}
CLIPBOARD.with(<_>::default);
match App::new(
{
if is_readable_stdin() {

View File

@ -2,7 +2,6 @@ use crate::channels::entry::{Entry, PreviewCommand};
use crate::preview::cache::PreviewCache;
use crate::preview::{Preview, PreviewContent};
use crate::utils::command::shell_command;
use lazy_static::lazy_static;
use parking_lot::Mutex;
use regex::Regex;
use rustc_hash::FxHashSet;
@ -11,12 +10,19 @@ use std::sync::Arc;
use tracing::debug;
#[allow(dead_code)]
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct CommandPreviewer {
cache: Arc<Mutex<PreviewCache>>,
config: CommandPreviewerConfig,
concurrent_preview_tasks: Arc<AtomicU8>,
in_flight_previews: Arc<Mutex<FxHashSet<String>>>,
command_re: Regex,
}
impl Default for CommandPreviewer {
fn default() -> Self {
CommandPreviewer::new(None)
}
}
#[allow(dead_code)]
@ -53,6 +59,7 @@ impl CommandPreviewer {
config,
concurrent_preview_tasks: Arc::new(AtomicU8::new(0)),
in_flight_previews: Arc::new(Mutex::new(FxHashSet::default())),
command_re: Regex::new(r"\{(\d+)\}").unwrap(),
}
}
@ -96,6 +103,7 @@ impl CommandPreviewer {
let concurrent_tasks = self.concurrent_preview_tasks.clone();
let command = command.clone();
let in_flight_previews = self.in_flight_previews.clone();
let command_re = self.command_re.clone();
tokio::spawn(async move {
try_preview(
&command,
@ -103,6 +111,7 @@ impl CommandPreviewer {
&cache,
&concurrent_tasks,
&in_flight_previews,
&command_re,
);
});
} else {
@ -114,11 +123,6 @@ impl CommandPreviewer {
}
}
lazy_static! {
static ref COMMAND_PLACEHOLDER_REGEX: Regex =
Regex::new(r"\{(\d+)\}").unwrap();
}
/// Format the command with the entry name and provided placeholders
///
/// # Example
@ -131,11 +135,15 @@ lazy_static! {
/// delimiter: ":".to_string(),
/// };
/// let entry = Entry::new("a:given:entry:to:preview".to_string(), PreviewType::Command(command.clone()));
/// let formatted_command = format_command(&command, &entry);
/// let formatted_command = format_command(&command, &entry, &regex::Regex::new(r"\{(\d+)\}").unwrap());
///
/// assert_eq!(formatted_command, "something 'a:given:entry:to:preview' 'entry' 'a'");
/// ```
pub fn format_command(command: &PreviewCommand, entry: &Entry) -> String {
pub fn format_command(
command: &PreviewCommand,
entry: &Entry,
command_re: &Regex,
) -> String {
let parts = entry.name.split(&command.delimiter).collect::<Vec<&str>>();
debug!("Parts: {:?}", parts);
@ -143,7 +151,7 @@ pub fn format_command(command: &PreviewCommand, entry: &Entry) -> String {
.command
.replace("{}", format!("'{}'", entry.name).as_str());
formatted_command = COMMAND_PLACEHOLDER_REGEX
formatted_command = command_re
.replace_all(&formatted_command, |caps: &regex::Captures| {
let index =
caps.get(1).unwrap().as_str().parse::<usize>().unwrap();
@ -160,9 +168,10 @@ pub fn try_preview(
cache: &Arc<Mutex<PreviewCache>>,
concurrent_tasks: &Arc<AtomicU8>,
in_flight_previews: &Arc<Mutex<FxHashSet<String>>>,
command_re: &Regex,
) {
debug!("Computing preview for {:?}", entry.name);
let command = format_command(command, entry);
let command = format_command(command, entry, command_re);
debug!("Formatted preview command: {:?}", command);
let child = shell_command()
@ -212,7 +221,11 @@ mod tests {
"an:entry:to:preview".to_string(),
PreviewType::Command(command.clone()),
);
let formatted_command = format_command(&command, &entry);
let formatted_command = format_command(
&command,
&entry,
&Regex::new(r"\{(\d+)\}").unwrap(),
);
assert_eq!(
formatted_command,
@ -230,7 +243,11 @@ mod tests {
"an:entry:to:preview".to_string(),
PreviewType::Command(command.clone()),
);
let formatted_command = format_command(&command, &entry);
let formatted_command = format_command(
&command,
&entry,
&Regex::new(r"\{(\d+)\}").unwrap(),
);
assert_eq!(formatted_command, "something");
}
@ -245,7 +262,11 @@ mod tests {
"an:entry:to:preview".to_string(),
PreviewType::Command(command.clone()),
);
let formatted_command = format_command(&command, &entry);
let formatted_command = format_command(
&command,
&entry,
&Regex::new(r"\{(\d+)\}").unwrap(),
);
assert_eq!(formatted_command, "something 'an:entry:to:preview'");
}
@ -260,7 +281,11 @@ mod tests {
"an:entry:to:preview".to_string(),
PreviewType::Command(command.clone()),
);
let formatted_command = format_command(&command, &entry);
let formatted_command = format_command(
&command,
&entry,
&Regex::new(r"\{(\d+)\}").unwrap(),
);
assert_eq!(formatted_command, "something 'an' -t 'to'");
}

View File

@ -8,12 +8,13 @@ use tracing::{debug, warn};
use tokio::sync::mpsc;
use crate::draw::Ctx;
use crate::screen::layout::Layout;
use crate::{action::Action, draw::draw, tui::Tui};
#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone)]
pub enum RenderingTask {
ClearScreen,
Render(Ctx),
Render(Box<Ctx>),
Resize(u16, u16),
Resume,
Suspend,
@ -35,9 +36,21 @@ impl IoStream {
}
}
#[derive(Default)]
pub struct UiState {
pub layout: Layout,
}
impl UiState {
pub fn new(layout: Layout) -> Self {
Self { layout }
}
}
pub async fn render(
mut render_rx: mpsc::UnboundedReceiver<RenderingTask>,
action_tx: mpsc::UnboundedSender<Action>,
ui_state_tx: mpsc::UnboundedSender<UiState>,
is_output_tty: bool,
) -> Result<()> {
let stream = if is_output_tty {
@ -73,13 +86,19 @@ pub async fn render(
if size.width.checked_mul(size.height).is_some() {
queue!(stderr(), BeginSynchronizedUpdate).ok();
tui.terminal.draw(|frame| {
if let Err(err) =
draw(&context, frame, frame.area())
{
warn!("Failed to draw: {:?}", err);
let _ = action_tx.send(Action::Error(
format!("Failed to draw: {err:?}"),
));
match draw(&context, frame, frame.area()) {
Ok(layout) => {
if layout != context.layout {
let _ = ui_state_tx
.send(UiState::new(layout));
}
}
Err(err) => {
warn!("Failed to draw: {:?}", err);
let _ = action_tx.send(Action::Error(
format!("Failed to draw: {err:?}"),
));
}
}
})?;
execute!(stderr(), EndSynchronizedUpdate).ok();

View File

@ -29,7 +29,7 @@ impl Default for Dimensions {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HelpBarLayout {
pub left: Rect,
pub middle: Rect,
@ -82,6 +82,7 @@ impl Display for PreviewTitlePosition {
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Layout {
pub help_bar: Option<HelpBarLayout>,
pub results: Rect,
@ -90,6 +91,17 @@ pub struct Layout {
pub remote_control: Option<Rect>,
}
impl Default for Layout {
/// Having a default layout with a non-zero height for the results area
/// is important for the initial rendering of the application. For the first
/// frame, this avoids not rendering any results at all since the picker's contents
/// depend on the height of the results area which is not known until the first
/// frame is rendered.
fn default() -> Self {
Self::new(None, Rect::new(0, 0, 0, 100), Rect::default(), None, None)
}
}
impl Layout {
#[allow(clippy::too_many_arguments)]
pub fn new(

View File

@ -10,18 +10,18 @@ use crate::draw::{ChannelState, Ctx, TvState};
use crate::input::convert_action_to_input_request;
use crate::picker::Picker;
use crate::preview::{PreviewState, Previewer};
use crate::render::UiState;
use crate::screen::colors::Colorscheme;
use crate::screen::layout::InputPosition;
use crate::screen::spinner::{Spinner, SpinnerState};
use crate::utils::clipboard::CLIPBOARD;
use crate::utils::metadata::AppMetadata;
use crate::utils::strings::EMPTY_STRING;
use anyhow::Result;
use copypasta::{ClipboardContext, ClipboardProvider};
use rustc_hash::{FxBuildHasher, FxHashSet};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use tokio::sync::mpsc::{Receiver, Sender, UnboundedSender};
use tracing::error;
use tokio::sync::mpsc::UnboundedSender;
#[derive(PartialEq, Copy, Clone, Hash, Eq, Debug, Serialize, Deserialize)]
pub enum Mode {
@ -39,7 +39,6 @@ pub struct Television {
pub current_pattern: String,
pub results_picker: Picker,
pub rc_picker: Picker,
results_area_height: u16,
pub previewer: Previewer,
pub preview_state: PreviewState,
pub spinner: Spinner,
@ -47,16 +46,7 @@ pub struct Television {
pub app_metadata: AppMetadata,
pub colorscheme: Colorscheme,
pub ticks: u64,
// these are really here as a means to communicate between the render thread
// and the main thread to update `Television`'s state without needing to pass
// a mutable reference to `draw`
pub inner_rx: Receiver<Message>,
pub inner_tx: Sender<Message>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Message {
ResultListHeightChanged(u16),
pub ui_state: UiState,
}
impl Television {
@ -88,8 +78,6 @@ impl Television {
channel.find(&input.unwrap_or(EMPTY_STRING.to_string()));
let spinner = Spinner::default();
// capacity is quite arbitrary here, we can adjust it later
let (inner_tx, inner_rx) = tokio::sync::mpsc::channel(10);
Self {
action_tx,
@ -102,7 +90,6 @@ impl Television {
current_pattern: EMPTY_STRING.to_string(),
results_picker,
rc_picker: Picker::default(),
results_area_height: 0,
previewer,
preview_state: PreviewState::default(),
spinner,
@ -110,11 +97,14 @@ impl Television {
app_metadata,
colorscheme,
ticks: 0,
inner_rx,
inner_tx,
ui_state: UiState::default(),
}
}
pub fn update_ui_state(&mut self, ui_state: UiState) {
self.ui_state = ui_state;
}
pub fn init_remote_control(&mut self) {
let cable_channels = load_cable_channels().unwrap_or_default();
let builtin_channels = load_builtin_channels(Some(
@ -135,7 +125,6 @@ impl Television {
let tv_state = TvState::new(
self.mode,
self.get_selected_entry(Some(Mode::Channel)),
self.results_area_height,
self.results_picker.clone(),
self.rc_picker.clone(),
channel_state,
@ -148,9 +137,9 @@ impl Television {
self.config.clone(),
self.colorscheme.clone(),
self.app_metadata.clone(),
self.inner_tx.clone(),
// now timestamp
std::time::Instant::now(),
self.ui_state.layout,
)
}
@ -230,7 +219,7 @@ impl Television {
picker.select_prev(
step,
result_count as usize,
self.results_area_height as usize,
self.ui_state.layout.results.height.saturating_sub(2) as usize,
);
}
@ -249,7 +238,7 @@ impl Television {
picker.select_next(
step,
result_count as usize,
self.results_area_height as usize,
self.ui_state.layout.results.height.saturating_sub(2) as usize,
);
}
@ -316,6 +305,7 @@ impl Television {
{
// preview content
if let Some(preview) = self.previewer.preview(selected_entry) {
// only update if the preview content has changed
if self.preview_state.preview.title != preview.title {
self.preview_state.update(
preview,
@ -324,7 +314,13 @@ impl Television {
.line_number
.unwrap_or(0)
.saturating_sub(
(self.results_area_height / 2).into(),
(self
.ui_state
.layout
.preview_window
.map_or(0, |w| w.height)
/ 2)
.into(),
)
.try_into()?,
selected_entry
@ -349,7 +345,7 @@ impl Television {
}
self.results_picker.entries = self.channel.results(
self.results_area_height.into(),
self.ui_state.layout.results.height.into(),
u32::try_from(self.results_picker.offset()).unwrap(),
);
self.results_picker.total_items = self.channel.result_count();
@ -365,7 +361,7 @@ impl Television {
self.rc_picker.entries = self.remote_control.results(
// this'll be more than the actual rc height but it's fine
self.results_area_height.into(),
self.ui_state.layout.results.height.into(),
u32::try_from(self.rc_picker.offset()).unwrap(),
);
self.rc_picker.total_items = self.remote_control.total_count();
@ -480,20 +476,13 @@ impl Television {
pub fn handle_copy_entry_to_clipboard(&mut self) {
if self.mode == Mode::Channel {
if let Some(entries) = self.get_selected_entries(None) {
if let Ok(mut ctx) = ClipboardContext::new() {
ctx.set_contents(
entries
.iter()
.map(|e| e.name.clone())
.collect::<Vec<_>>()
.join(" "),
)
.unwrap_or_else(|_| {
error!("Could not copy to clipboard");
});
} else {
error!("Could not copy to clipboard");
}
let copied_string = entries
.iter()
.map(|e| e.name.clone())
.collect::<Vec<_>>()
.join(" ");
tokio::spawn(CLIPBOARD.set(copied_string));
}
}
}
@ -521,11 +510,25 @@ impl Television {
}
Action::SelectNextPage => {
self.preview_state.reset();
self.select_next_entry(self.results_area_height.into());
self.select_next_entry(
self.ui_state
.layout
.results
.height
.saturating_sub(2)
.into(),
);
}
Action::SelectPrevPage => {
self.preview_state.reset();
self.select_prev_entry(self.results_area_height.into());
self.select_prev_entry(
self.ui_state
.layout
.results
.height
.saturating_sub(2)
.into(),
);
}
Action::ScrollPreviewDown => self.preview_state.scroll_down(1),
Action::ScrollPreviewUp => self.preview_state.scroll_up(1),
@ -567,13 +570,6 @@ impl Television {
///
/// This function may return an Action that'll be processed by the parent `App`.
pub fn update(&mut self, action: &Action) -> Result<Option<Action>> {
if let Ok(Message::ResultListHeightChanged(height)) =
self.inner_rx.try_recv()
{
self.results_area_height = height;
self.action_tx.send(Action::Render)?;
}
self.handle_action(action)?;
self.update_results_picker_state();

View File

@ -0,0 +1,177 @@
/*
MIT License
Copyright (c) 2023 - sxyazi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
use std::ffi::OsString;
use crate::utils::rocell::RoCell;
use parking_lot::Mutex;
pub static CLIPBOARD: RoCell<Clipboard> = RoCell::new();
#[derive(Default)]
pub struct Clipboard {
content: Mutex<OsString>,
}
impl Clipboard {
#[cfg(unix)]
pub async fn get(&self) -> OsString {
use std::os::unix::prelude::OsStringExt;
use tokio::process::Command;
let all = [
("pbpaste", &[][..]),
("termux-clipboard-get", &[]),
("wl-paste", &[]),
("xclip", &["-o", "-selection", "clipboard"]),
("xsel", &["-ob"]),
];
for (bin, args) in all {
let Ok(output) = Command::new(bin)
.args(args)
.kill_on_drop(true)
.output()
.await
else {
continue;
};
if output.status.success() {
return OsString::from_vec(output.stdout);
}
}
self.content.lock().clone()
}
#[cfg(windows)]
pub async fn get(&self) -> OsString {
use clipboard_win::{formats, get_clipboard};
let result = tokio::task::spawn_blocking(|| {
get_clipboard::<String, _>(formats::Unicode)
});
if let Ok(Ok(s)) = result.await {
return s.into();
}
self.content.lock().clone()
}
#[cfg(unix)]
pub async fn set(&self, s: impl AsRef<std::ffi::OsStr>) {
use std::{
io::{stderr, BufWriter},
process::Stdio,
};
use crossterm::execute;
use tokio::{io::AsyncWriteExt, process::Command};
s.as_ref().clone_into(&mut self.content.lock());
execute!(
BufWriter::new(stderr()),
osc52::SetClipboard::new(s.as_ref())
)
.ok();
let all = [
("pbcopy", &[][..]),
("termux-clipboard-set", &[]),
("wl-copy", &[]),
("xclip", &["-selection", "clipboard"]),
("xsel", &["-ib"]),
];
for (bin, args) in all {
let cmd = Command::new(bin)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.kill_on_drop(true)
.spawn();
let Ok(mut child) = cmd else { continue };
let mut stdin = child.stdin.take().unwrap();
if stdin
.write_all(s.as_ref().as_encoded_bytes())
.await
.is_err()
{
continue;
}
drop(stdin);
if child.wait().await.is_ok_and(|s| s.success()) {
break;
}
}
}
#[cfg(windows)]
pub async fn set(&self, s: impl AsRef<std::ffi::OsStr>) {
use clipboard_win::{formats, set_clipboard};
let s = s.as_ref().to_owned();
*self.content.lock() = s.clone();
tokio::task::spawn_blocking(move || {
set_clipboard(formats::Unicode, s.to_string_lossy())
})
.await
.ok();
}
}
#[cfg(unix)]
mod osc52 {
use std::ffi::OsStr;
use base64::{engine::general_purpose, Engine};
#[derive(Debug)]
pub struct SetClipboard {
content: String,
}
impl SetClipboard {
pub fn new(content: &OsStr) -> Self {
Self {
content: general_purpose::STANDARD
.encode(content.as_encoded_bytes()),
}
}
}
impl crossterm::Command for SetClipboard {
fn write_ansi(
&self,
f: &mut impl std::fmt::Write,
) -> std::fmt::Result {
write!(f, "\x1b]52;c;{}\x1b\\", self.content)
}
}
}

View File

@ -6,9 +6,9 @@ use std::io::BufReader;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use std::sync::OnceLock;
use ignore::{overrides::Override, types::TypesBuilder, WalkBuilder};
use lazy_static::lazy_static;
use tracing::{debug, warn};
use crate::utils::strings::{
@ -61,8 +61,10 @@ where
}
}
lazy_static::lazy_static! {
pub static ref DEFAULT_NUM_THREADS: usize = default_num_threads().into();
pub static DEFAULT_NUM_THREADS: OnceLock<usize> = OnceLock::new();
pub fn get_default_num_threads() -> usize {
*DEFAULT_NUM_THREADS.get_or_init(default_num_threads)
}
pub fn walk_builder(
@ -147,342 +149,347 @@ where
path.as_ref()
.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| KNOWN_TEXT_FILE_EXTENSIONS.contains(ext))
.is_some_and(|ext| get_known_text_file_extensions().contains(ext))
}
lazy_static! {
static ref KNOWN_TEXT_FILE_EXTENSIONS: FxHashSet<&'static str> = [
"ada",
"adb",
"ads",
"applescript",
"as",
"asc",
"ascii",
"ascx",
"asm",
"asmx",
"asp",
"aspx",
"atom",
"au3",
"awk",
"bas",
"bash",
"bashrc",
"bat",
"bbcolors",
"bcp",
"bdsgroup",
"bdsproj",
"bib",
"bowerrc",
"c",
"cbl",
"cc",
"cfc",
"cfg",
"cfm",
"cfml",
"cgi",
"cjs",
"clj",
"cljs",
"cls",
"cmake",
"cmd",
"cnf",
"cob",
"code-snippets",
"coffee",
"coffeekup",
"conf",
"cp",
"cpp",
"cpt",
"cpy",
"crt",
"cs",
"csh",
"cson",
"csproj",
"csr",
"css",
"csslintrc",
"csv",
"ctl",
"curlrc",
"cxx",
"d",
"dart",
"dfm",
"diff",
"dof",
"dpk",
"dpr",
"dproj",
"dtd",
"eco",
"editorconfig",
"ejs",
"el",
"elm",
"emacs",
"eml",
"ent",
"erb",
"erl",
"eslintignore",
"eslintrc",
"ex",
"exs",
"f",
"f03",
"f77",
"f90",
"f95",
"fish",
"for",
"fpp",
"frm",
"fs",
"fsproj",
"fsx",
"ftn",
"gemrc",
"gemspec",
"gitattributes",
"gitconfig",
"gitignore",
"gitkeep",
"gitmodules",
"go",
"gpp",
"gradle",
"graphql",
"groovy",
"groupproj",
"grunit",
"gtmpl",
"gvimrc",
"h",
"haml",
"hbs",
"hgignore",
"hh",
"hpp",
"hrl",
"hs",
"hta",
"htaccess",
"htc",
"htm",
"html",
"htpasswd",
"hxx",
"iced",
"iml",
"inc",
"inf",
"info",
"ini",
"ino",
"int",
"irbrc",
"itcl",
"itermcolors",
"itk",
"jade",
"java",
"jhtm",
"jhtml",
"js",
"jscsrc",
"jshintignore",
"jshintrc",
"json",
"json5",
"jsonld",
"jsp",
"jspx",
"jsx",
"ksh",
"less",
"lhs",
"lisp",
"log",
"ls",
"lsp",
"lua",
"m",
"m4",
"mak",
"map",
"markdown",
"master",
"md",
"mdown",
"mdwn",
"mdx",
"metadata",
"mht",
"mhtml",
"mjs",
"mk",
"mkd",
"mkdn",
"mkdown",
"ml",
"mli",
"mm",
"mxml",
"nfm",
"nfo",
"noon",
"npmignore",
"npmrc",
"nuspec",
"nvmrc",
"ops",
"pas",
"pasm",
"patch",
"pbxproj",
"pch",
"pem",
"pg",
"php",
"php3",
"php4",
"php5",
"phpt",
"phtml",
"pir",
"pl",
"pm",
"pmc",
"pod",
"pot",
"prettierrc",
"properties",
"props",
"pt",
"pug",
"purs",
"py",
"pyx",
"r",
"rake",
"rb",
"rbw",
"rc",
"rdoc",
"rdoc_options",
"resx",
"rexx",
"rhtml",
"rjs",
"rlib",
"ron",
"rs",
"rss",
"rst",
"rtf",
"rvmrc",
"rxml",
"s",
"sass",
"scala",
"scm",
"scss",
"seestyle",
"sh",
"shtml",
"sln",
"sls",
"spec",
"sql",
"sqlite",
"sqlproj",
"srt",
"ss",
"sss",
"st",
"strings",
"sty",
"styl",
"stylus",
"sub",
"sublime-build",
"sublime-commands",
"sublime-completions",
"sublime-keymap",
"sublime-macro",
"sublime-menu",
"sublime-project",
"sublime-settings",
"sublime-workspace",
"sv",
"svc",
"svg",
"swift",
"t",
"tcl",
"tcsh",
"terminal",
"tex",
"text",
"textile",
"tg",
"tk",
"tmLanguage",
"tmpl",
"tmTheme",
"toml",
"tpl",
"ts",
"tsv",
"tsx",
"tt",
"tt2",
"ttml",
"twig",
"txt",
"v",
"vb",
"vbproj",
"vbs",
"vcproj",
"vcxproj",
"vh",
"vhd",
"vhdl",
"vim",
"viminfo",
"vimrc",
"vm",
"vue",
"webapp",
"webmanifest",
"wsc",
"x-php",
"xaml",
"xht",
"xhtml",
"xml",
"xs",
"xsd",
"xsl",
"xslt",
"y",
"yaml",
"yml",
"zsh",
"zshrc",
]
.iter()
.copied()
.collect();
pub static KNOWN_TEXT_FILE_EXTENSIONS: OnceLock<FxHashSet<&'static str>> =
OnceLock::new();
pub fn get_known_text_file_extensions() -> &'static FxHashSet<&'static str> {
KNOWN_TEXT_FILE_EXTENSIONS.get_or_init(|| {
[
"ada",
"adb",
"ads",
"applescript",
"as",
"asc",
"ascii",
"ascx",
"asm",
"asmx",
"asp",
"aspx",
"atom",
"au3",
"awk",
"bas",
"bash",
"bashrc",
"bat",
"bbcolors",
"bcp",
"bdsgroup",
"bdsproj",
"bib",
"bowerrc",
"c",
"cbl",
"cc",
"cfc",
"cfg",
"cfm",
"cfml",
"cgi",
"cjs",
"clj",
"cljs",
"cls",
"cmake",
"cmd",
"cnf",
"cob",
"code-snippets",
"coffee",
"coffeekup",
"conf",
"cp",
"cpp",
"cpt",
"cpy",
"crt",
"cs",
"csh",
"cson",
"csproj",
"csr",
"css",
"csslintrc",
"csv",
"ctl",
"curlrc",
"cxx",
"d",
"dart",
"dfm",
"diff",
"dof",
"dpk",
"dpr",
"dproj",
"dtd",
"eco",
"editorconfig",
"ejs",
"el",
"elm",
"emacs",
"eml",
"ent",
"erb",
"erl",
"eslintignore",
"eslintrc",
"ex",
"exs",
"f",
"f03",
"f77",
"f90",
"f95",
"fish",
"for",
"fpp",
"frm",
"fs",
"fsproj",
"fsx",
"ftn",
"gemrc",
"gemspec",
"gitattributes",
"gitconfig",
"gitignore",
"gitkeep",
"gitmodules",
"go",
"gpp",
"gradle",
"graphql",
"groovy",
"groupproj",
"grunit",
"gtmpl",
"gvimrc",
"h",
"haml",
"hbs",
"hgignore",
"hh",
"hpp",
"hrl",
"hs",
"hta",
"htaccess",
"htc",
"htm",
"html",
"htpasswd",
"hxx",
"iced",
"iml",
"inc",
"inf",
"info",
"ini",
"ino",
"int",
"irbrc",
"itcl",
"itermcolors",
"itk",
"jade",
"java",
"jhtm",
"jhtml",
"js",
"jscsrc",
"jshintignore",
"jshintrc",
"json",
"json5",
"jsonld",
"jsp",
"jspx",
"jsx",
"ksh",
"less",
"lhs",
"lisp",
"log",
"ls",
"lsp",
"lua",
"m",
"m4",
"mak",
"map",
"markdown",
"master",
"md",
"mdown",
"mdwn",
"mdx",
"metadata",
"mht",
"mhtml",
"mjs",
"mk",
"mkd",
"mkdn",
"mkdown",
"ml",
"mli",
"mm",
"mxml",
"nfm",
"nfo",
"noon",
"npmignore",
"npmrc",
"nuspec",
"nvmrc",
"ops",
"pas",
"pasm",
"patch",
"pbxproj",
"pch",
"pem",
"pg",
"php",
"php3",
"php4",
"php5",
"phpt",
"phtml",
"pir",
"pl",
"pm",
"pmc",
"pod",
"pot",
"prettierrc",
"properties",
"props",
"pt",
"pug",
"purs",
"py",
"pyx",
"r",
"rake",
"rb",
"rbw",
"rc",
"rdoc",
"rdoc_options",
"resx",
"rexx",
"rhtml",
"rjs",
"rlib",
"ron",
"rs",
"rss",
"rst",
"rtf",
"rvmrc",
"rxml",
"s",
"sass",
"scala",
"scm",
"scss",
"seestyle",
"sh",
"shtml",
"sln",
"sls",
"spec",
"sql",
"sqlite",
"sqlproj",
"srt",
"ss",
"sss",
"st",
"strings",
"sty",
"styl",
"stylus",
"sub",
"sublime-build",
"sublime-commands",
"sublime-completions",
"sublime-keymap",
"sublime-macro",
"sublime-menu",
"sublime-project",
"sublime-settings",
"sublime-workspace",
"sv",
"svc",
"svg",
"swift",
"t",
"tcl",
"tcsh",
"terminal",
"tex",
"text",
"textile",
"tg",
"tk",
"tmLanguage",
"tmpl",
"tmTheme",
"toml",
"tpl",
"ts",
"tsv",
"tsx",
"tt",
"tt2",
"ttml",
"twig",
"txt",
"v",
"vb",
"vbproj",
"vbs",
"vcproj",
"vcxproj",
"vh",
"vhd",
"vhdl",
"vim",
"viminfo",
"vimrc",
"vm",
"vue",
"webapp",
"webmanifest",
"wsc",
"x-php",
"xaml",
"xht",
"xhtml",
"xml",
"xs",
"xsd",
"xsl",
"xslt",
"y",
"yaml",
"yml",
"zsh",
"zshrc",
]
.iter()
.copied()
.collect()
})
}
pub fn is_accepted_image_extension<P>(path: P) -> bool

View File

@ -1,4 +1,5 @@
pub mod cache;
pub mod clipboard;
pub mod command;
pub mod files;
pub mod hashmaps;
@ -6,6 +7,7 @@ pub mod image;
pub mod indices;
pub mod input;
pub mod metadata;
pub mod rocell;
pub mod shell;
pub mod stdin;
pub mod strings;

View File

@ -0,0 +1,99 @@
/*
MIT License
Copyright (c) 2023 - sxyazi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
use std::{
cell::UnsafeCell,
fmt::{self, Display},
mem,
ops::Deref,
};
// Read-only cell. It's safe to use this in a static variable, but it's not safe
// to mutate it. This is useful for storing static data that is expensive to
// initialize, but is immutable once.
pub struct RoCell<T>(UnsafeCell<Option<T>>);
unsafe impl<T> Sync for RoCell<T> {}
impl<T> RoCell<T> {
#[inline]
pub const fn new() -> Self {
Self(UnsafeCell::new(None))
}
#[inline]
pub const fn new_const(value: T) -> Self {
Self(UnsafeCell::new(Some(value)))
}
#[inline]
pub fn init(&self, value: T) {
debug_assert!(!self.initialized());
unsafe {
*self.0.get() = Some(value);
}
}
#[inline]
pub fn with<F>(&self, f: F)
where
F: FnOnce() -> T,
{
self.init(f());
}
#[inline]
pub fn drop(&self) -> T {
debug_assert!(self.initialized());
unsafe { mem::take(&mut *self.0.get()).unwrap_unchecked() }
}
#[inline]
fn initialized(&self) -> bool {
unsafe { (*self.0.get()).is_some() }
}
}
impl<T> Default for RoCell<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Deref for RoCell<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
debug_assert!(self.initialized());
unsafe { (*self.0.get()).as_ref().unwrap_unchecked() }
}
}
impl<T> Display for RoCell<T>
where
T: Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.deref().fmt(f)
}
}

View File

@ -1,5 +1,3 @@
use lazy_static::lazy_static;
/// Returns the index of the next character boundary in the given string.
///
/// If the given index is already a character boundary, it is returned as is.
@ -151,14 +149,11 @@ pub fn try_parse_utf8_char(input: &[u8]) -> Option<(char, usize)> {
decoded.map(|(seq, n)| (seq.chars().next().unwrap(), n))
}
lazy_static! {
/// The Unicode symbol to use for non-printable characters.
static ref NULL_SYMBOL: char = char::from_u32(0x2400).unwrap();
}
pub const EMPTY_STRING: &str = "";
pub const TAB_WIDTH: usize = 4;
/// The Unicode symbol to use for non-printable characters.
const NULL_SYMBOL: char = '\u{2400}';
const TAB_CHARACTER: char = '\t';
const LINE_FEED_CHARACTER: char = '\x0A';
const DELETE_CHARACTER: char = '\x7F';
@ -304,7 +299,7 @@ pub fn replace_non_printable(
| BOM_CHARACTER
if config.replace_control_characters =>
{
output.push(*NULL_SYMBOL);
output.push(NULL_SYMBOL);
}
// CJK Unified Ideographs
// ex: 解
@ -337,13 +332,13 @@ pub fn replace_non_printable(
}
// Unicode characters above 0x0700 seem unstable with ratatui
c if c > '\u{0700}' => {
output.push(*NULL_SYMBOL);
output.push(NULL_SYMBOL);
}
// everything else
c => output.push(c),
}
} else {
output.push(*NULL_SYMBOL);
output.push(NULL_SYMBOL);
idx += 1;
}
}

View File

@ -224,7 +224,6 @@ pub fn compute_highlights_for_line<'a>(
// SOFTWARE.
use directories::BaseDirs;
use lazy_static::lazy_static;
#[cfg(target_os = "macos")]
use std::env;
@ -258,13 +257,11 @@ impl BatProjectDirs {
}
}
lazy_static! {
pub static ref PROJECT_DIRS: BatProjectDirs = BatProjectDirs::new()
.unwrap_or_else(|| panic!("Could not get home directory"));
}
pub fn load_highlighting_assets() -> HighlightingAssets {
HighlightingAssets::from_cache(PROJECT_DIRS.cache_dir())
let project_dirs = BatProjectDirs::new()
.unwrap_or_else(|| panic!("Could not get home directory"));
HighlightingAssets::from_cache(project_dirs.cache_dir())
.unwrap_or_else(|_| HighlightingAssets::from_binary())
}

View File

@ -5,7 +5,7 @@ use std::num::NonZeroUsize;
/// This will use the number of available threads if possible, but will default to 1 if the number
/// of available threads cannot be determined. It will also never use more than 32 threads to avoid
/// startup overhead.
pub fn default_num_threads() -> NonZeroUsize {
pub fn default_num_threads() -> usize {
// default to 1 thread if we can't determine the number of available threads
let default = NonZeroUsize::MIN;
// never use more than 32 threads to avoid startup overhead
@ -14,4 +14,5 @@ pub fn default_num_threads() -> NonZeroUsize {
std::thread::available_parallelism()
.unwrap_or(default)
.min(limit)
.get()
}