diff --git a/Cargo.lock b/Cargo.lock index 4d4407d..e9b7fd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index d83f704..0095f0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } diff --git a/flake.nix b/flake.nix index 4746e2b..a8e48d8 100644 --- a/flake.nix +++ b/flake.nix @@ -33,7 +33,7 @@ pkgs.rustChannelOf { rustToolchain = ./rust-toolchain.toml; - sha256 = "VZZnlyP69+Y3crrLHQyJirqlHrTtGTsyiSnZB8jEvVo="; + sha256 = "s1RPtyvDGJaX/BisLT+ifVfuhDT1nZkZ1NcK8sbwELM="; } ) .rust; diff --git a/television/app.rs b/television/app.rs index 65ae958..f1a7c16 100644 --- a/television/app.rs +++ b/television/app.rs @@ -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, + /// A channel that listens to UI updates. + ui_state_rx: mpsc::UnboundedReceiver, + ui_state_tx: mpsc::UnboundedSender, } /// 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); + } } _ => {} } diff --git a/television/channels/cable.rs b/television/channels/cable.rs index 14c7162..4546c9d 100644 --- a/television/channels/cable.rs +++ b/television/channels/cable.rs @@ -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 for Channel { } } -lazy_static! { - static ref BUILTIN_PREVIEW_RE: Regex = Regex::new(r"^:(\w+):$").unwrap(); -} - fn parse_preview_kind(command: &PreviewCommand) -> Result { 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 { diff --git a/television/channels/dirs.rs b/television/channels/dirs.rs index 26b62dd..16bd4ff 100644 --- a/television/channels/dirs.rs +++ b/television/channels/dirs.rs @@ -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, injector: Injector) { } 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); }); diff --git a/television/channels/files.rs b/television/channels/files.rs index ef09b07..6e7a2ff 100644 --- a/television/channels/files.rs +++ b/television/channels/files.rs @@ -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, injector: Injector) { } 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); }); diff --git a/television/channels/git_repos.rs b/television/channels/git_repos.rs index fec62f0..f8cebf5 100644 --- a/television/channels/git_repos.rs +++ b/television/channels/git_repos.rs @@ -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, icon: FileIcon, crawl_handle: JoinHandle<()>, selected_entries: FxHashSet, + 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 { 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) { 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()), ) diff --git a/television/channels/text.rs b/television/channels/text.rs index 97024df..f5dbe87 100644 --- a/television/channels/text.rs +++ b/television/channels/text.rs @@ -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); }); diff --git a/television/config/mod.rs b/television/config/mod.rs index f3bdeac..451695d 100644 --- a/television/config/mod.rs +++ b/television/config/mod.rs @@ -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 = - // 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 = - // 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) { diff --git a/television/config/themes.rs b/television/config/themes.rs index 2708278..8f64558 100644 --- a/television/config/themes.rs +++ b/television/config/themes.rs @@ -128,10 +128,10 @@ impl Theme { pub fn from_builtin( name: &str, ) -> Result> { - 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) } diff --git a/television/config/themes/builtin.rs b/television/config/themes/builtin.rs index bb6826d..081d481 100644 --- a/television/config/themes/builtin.rs +++ b/television/config/themes/builtin.rs @@ -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 } diff --git a/television/draw.rs b/television/draw.rs index 3846791..5c93690 100644 --- a/television/draw.rs +++ b/television/draw.rs @@ -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, - 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, - 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, 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, 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 { 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) } diff --git a/television/logging.rs b/television/logging.rs index 8913fcc..1ed37fe 100644 --- a/television/logging.rs +++ b/television/logging.rs @@ -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) diff --git a/television/main.rs b/television/main.rs index 9eed959..d60b447 100644 --- a/television/main.rs +++ b/television/main.rs @@ -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() { diff --git a/television/preview/previewers/command.rs b/television/preview/previewers/command.rs index 122c08a..b239fa7 100644 --- a/television/preview/previewers/command.rs +++ b/television/preview/previewers/command.rs @@ -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>, config: CommandPreviewerConfig, concurrent_preview_tasks: Arc, in_flight_previews: Arc>>, + 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, ®ex::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::>(); 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: ®ex::Captures| { let index = caps.get(1).unwrap().as_str().parse::().unwrap(); @@ -160,9 +168,10 @@ pub fn try_preview( cache: &Arc>, concurrent_tasks: &Arc, in_flight_previews: &Arc>>, + 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'"); } diff --git a/television/render.rs b/television/render.rs index 036175e..fa6dd9f 100644 --- a/television/render.rs +++ b/television/render.rs @@ -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), 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, action_tx: mpsc::UnboundedSender, + ui_state_tx: mpsc::UnboundedSender, 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(); diff --git a/television/screen/layout.rs b/television/screen/layout.rs index 7049317..9197df0 100644 --- a/television/screen/layout.rs +++ b/television/screen/layout.rs @@ -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, pub results: Rect, @@ -90,6 +91,17 @@ pub struct Layout { pub remote_control: Option, } +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( diff --git a/television/television.rs b/television/television.rs index c5acc44..07161e6 100644 --- a/television/television.rs +++ b/television/television.rs @@ -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, - pub inner_tx: Sender, -} - -#[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::>() - .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::>() + .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> { - 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(); diff --git a/television/utils/clipboard.rs b/television/utils/clipboard.rs new file mode 100644 index 0000000..86c58d2 --- /dev/null +++ b/television/utils/clipboard.rs @@ -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 = RoCell::new(); + +#[derive(Default)] +pub struct Clipboard { + content: Mutex, +} + +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::(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) { + 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) { + 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) + } + } +} diff --git a/television/utils/files.rs b/television/utils/files.rs index ee83697..4e97e02 100644 --- a/television/utils/files.rs +++ b/television/utils/files.rs @@ -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 = 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> = + 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

(path: P) -> bool diff --git a/television/utils/mod.rs b/television/utils/mod.rs index 74c26cf..9192f4a 100644 --- a/television/utils/mod.rs +++ b/television/utils/mod.rs @@ -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; diff --git a/television/utils/rocell.rs b/television/utils/rocell.rs new file mode 100644 index 0000000..d3758e6 --- /dev/null +++ b/television/utils/rocell.rs @@ -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(UnsafeCell>); + +unsafe impl Sync for RoCell {} + +impl RoCell { + #[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(&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 Default for RoCell { + fn default() -> Self { + Self::new() + } +} + +impl Deref for RoCell { + type Target = T; + + fn deref(&self) -> &Self::Target { + debug_assert!(self.initialized()); + unsafe { (*self.0.get()).as_ref().unwrap_unchecked() } + } +} + +impl Display for RoCell +where + T: Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.deref().fmt(f) + } +} diff --git a/television/utils/strings.rs b/television/utils/strings.rs index be63aa2..1610f1e 100644 --- a/television/utils/strings.rs +++ b/television/utils/strings.rs @@ -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; } } diff --git a/television/utils/syntax.rs b/television/utils/syntax.rs index 5ca69f5..6b2f5c1 100644 --- a/television/utils/syntax.rs +++ b/television/utils/syntax.rs @@ -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()) } diff --git a/television/utils/threads.rs b/television/utils/threads.rs index 64d2c09..160b0da 100644 --- a/television/utils/threads.rs +++ b/television/utils/threads.rs @@ -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() }