commit 644ec6e2b11adf2ffb5a064d13ebd4da8870db5e Author: TomZz Date: Mon Jun 8 15:17:18 2026 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e89d4af --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target/ +.DS_Store diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6313638 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,9757 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "accesskit" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5351dcebb14b579ccab05f288596b2ae097005be7ee50a7c3d4ca9d0d5a66f6a" +dependencies = [ + "uuid", +] + +[[package]] +name = "accesskit_atspi_common" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8c61bee90b42a772d39d06a740207dc71a4e780004ace1db8d99fb1baaa954" +dependencies = [ + "accesskit", + "accesskit_consumer 0.36.0", + "atspi-common", + "phf 0.13.1", + "serde", + "zvariant", +] + +[[package]] +name = "accesskit_consumer" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_consumer" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e0d7e25d06f4dc21d1774d67146e9e80d6789216cbd4d1e88185b0095dba60" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_macos" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c5c87e8d94f2ec10cce590aadff24c76f576dab5502d45d0aed9fc3065d4451" +dependencies = [ + "accesskit", + "accesskit_consumer 0.36.0", + "hashbrown 0.16.1", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b016ca8db0ea0ea2ceff29a9d6240391492d960716aa471967c00e8cc8cb197c" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel 2.5.0", + "async-executor", + "async-task", + "atspi", + "futures-lite 2.6.1", + "futures-util", + "serde", + "zbus", +] + +[[package]] +name = "accesskit_windows" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff7009f1a532e917d66970a1e80c965140c6cfbbabbdde3d64e5431e6c78e21" +dependencies = [ + "accesskit", + "accesskit_consumer 0.35.0", + "hashbrown 0.16.1", + "static_assertions", + "windows 0.62.2", + "windows-core 0.62.2", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alacritty_terminal" +version = "0.26.1-dev" +source = "git+https://github.com/zed-industries/alacritty?rev=fcf32feacb367b75ec84dd40f041e4fd411d3cc1#fcf32feacb367b75ec84dd40f041e4fd411d3cc1" +dependencies = [ + "base64", + "bitflags 2.12.1", + "home", + "libc", + "log", + "miow", + "parking_lot", + "piper", + "polling", + "regex-automata", + "rustix 1.1.4", + "rustix-openpty", + "serde", + "signal-hook", + "unicode-width", + "vte", + "windows-sys 0.59.0", +] + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "ashell" +version = "0.1.0" +dependencies = [ + "alacritty_terminal", + "anyhow", + "async-trait", + "chrono", + "directories", + "flate2", + "gpui", + "gpui-component", + "gpui-component-assets", + "gpui_platform", + "menu", + "portable-pty", + "rfd", + "russh", + "russh-keys", + "russh-sftp", + "serde", + "serde_json", + "ssh-key", + "sysinfo 0.33.1", + "tar", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-subscriber", + "uuid", + "walkdir", + "zip", +] + +[[package]] +name = "ashpd" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f3f79755c74fd155000314eb349864caa787c6592eace6c6882dad873d9c39" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.4", + "raw-window-handle", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "ashpd" +version = "0.13.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340e0f6bf7f9ee78549c61454f1460a3ed97c011902ee76b58301bbc6d502a32" +dependencies = [ + "enumflags2", + "futures-util", + "getrandom 0.4.2", + "serde", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-io", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.4.1", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite 2.6.1", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite 2.6.1", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite 2.6.1", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel 2.5.0", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.4.1", + "futures-lite 2.6.1", + "rustix 1.1.4", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.4", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 2.6.1", + "gloo-timers 0.3.0", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-tar" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1937db2d56578aa3919b9bdb0e5100693fd7d1c0f145c53eb81fbb03e217550" +dependencies = [ + "async-std", + "filetime", + "libc", + "pin-project", + "redox_syscall 0.2.16", + "xattr 0.2.3", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async_zip" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c50d65ce1b0e0cb65a785ff615f78860d7754290647d3b983208daa4f85e6" +dependencies = [ + "async-compression", + "crc32fast", + "futures-lite 2.6.1", + "pin-project", + "thiserror 2.0.18", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atspi" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" +dependencies = [ + "atspi-common", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "atspi-proxies" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" +dependencies = [ + "atspi-common", + "serde", + "zbus", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey", + "rayon", + "thiserror 2.0.18", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base62" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd637ac531c60eb7fbc4684dc061c2d7d90d73d758181aa02eeff0464b9eee4b" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bcrypt-pbkdf" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2" +dependencies = [ + "blowfish", + "pbkdf2", + "sha2", +] + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags 2.12.1", + "cexpr", + "clang-sys", + "itertools 0.11.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 2.1.2", + "shlex 1.3.0", + "syn", +] + +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.4", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "bytes", + "cfg_aliases", +] + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "built" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags 2.12.1", + "polling", + "rustix 1.1.4", + "slab", + "tracing", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cbindgen" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" +dependencies = [ + "heck 0.4.1", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "tempfile", + "toml 0.8.23", +] + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cocoa" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6140449f97a6e97f9511815c5632d84c8aacf8ac271ad77c559218161a1373c" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation 0.1.2", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +dependencies = [ + "bitflags 2.12.1", + "block", + "cocoa-foundation 0.2.0", + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" +dependencies = [ + "bitflags 2.12.1", + "block", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "libc", + "objc", +] + +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "serde", + "termcolor", + "unicode-width", +] + +[[package]] +name = "collections" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "gpui_util", + "indexmap", + "rustc-hash 2.1.2", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "command-fds" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b60b5124979fccd9addd89d8b97a1d6eebb4950694520c75ddd722535ea443f" +dependencies = [ + "nix 0.31.3", + "thiserror 2.0.18", +] + +[[package]] +name = "compression-codecs" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" +dependencies = [ + "bzip2", + "compression-core", + "deflate64", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" + +[[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_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.12.1", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-helmer-fork" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32eb7c354ae9f6d437a6039099ce7ecd049337a8109b23d73e48e8ffba8e9cd5" +dependencies = [ + "bitflags 2.12.1", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.12.1", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "core-graphics2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4416167a69126e617f8d0a214af0e3c1dbdeffcb100ddf72dcd1a1ac9893c146" +dependencies = [ + "bitflags 2.12.1", + "block", + "cfg-if", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "core-text" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a593227b66cbd4007b2a050dfdd9e1d1318311409c8d600dc82ba1b15ca9c130" +dependencies = [ + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139679cc63eb9504bdbe37e37874b0247136177655f0008588781e90863afa62" +dependencies = [ + "block", + "core-foundation 0.10.0", + "core-graphics2", + "io-surface", + "libc", + "metal", +] + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "cosmic-text" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be17b688510d934ce13f48a2beba700e11583e281e0fda99c22bb256a14eda73" +dependencies = [ + "bitflags 2.12.1", + "fontdb", + "harfrust", + "linebender_resource_handle", + "log", + "rangemap", + "rustc-hash 2.1.2", + "self_cell", + "skrifa", + "smol_str", + "swash", + "sys-locale", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctor" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01334b89b69ff726750c5ce5073fc8bd860e99aa9a8fc5ca11b04730e3aee97a" +dependencies = [ + "link-section", + "linktime-proc-macro", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "data-url" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" + +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "derive_refineable" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.60.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.12.1", + "block2 0.6.2", + "libc", + "objc2 0.6.4", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dwrote" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b35532432acc8b19ceed096e35dfa088d3ea037fe4f3c085f1f97f33b4d02" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "embed-resource" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31a88c8d26de40ed18fe748c547845aa39de1db3afd958f8cb91579f3644bcb" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.2+spec-1.1.0", + "vswhom", + "winreg 0.55.0", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enum-iterator" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4549325971814bda7a44061bf3fe7e487d447cba01e4220a4b454d630d7a016" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "etagere" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" +dependencies = [ + "euclid", + "svg_fmt", +] + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "exr" +version = "1.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "filedescriptor" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" +dependencies = [ + "libc", + "thiserror 1.0.69", + "winapi", +] + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" + +[[package]] +name = "float-ord" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-types" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "freetype-sys" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-concurrency" +version = "7.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175cd8cca9e1d45b87f18ffa75088f2099e3c4fe5e2f83e42de112560bea8ea6" +dependencies = [ + "fixedbitset", + "futures-core", + "futures-lite 2.6.1", + "pin-project", + "smallvec", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.4.1", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gif" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-timers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "482ce8a491a501da4cd806bd190275363d674f2845005c6ddbd5d3e1dd54495d" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + +[[package]] +name = "gpu-allocator" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" +dependencies = [ + "ash", + "hashbrown 0.16.1", + "log", + "presser", + "thiserror 2.0.18", + "windows 0.58.0", +] + +[[package]] +name = "gpu-descriptor" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" +dependencies = [ + "bitflags 2.12.1", + "gpu-descriptor-types", + "hashbrown 0.15.5", +] + +[[package]] +name = "gpu-descriptor-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" +dependencies = [ + "bitflags 2.12.1", +] + +[[package]] +name = "gpui" +version = "0.2.2" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "accesskit", + "anyhow", + "async-channel 2.5.0", + "async-task", + "bindgen", + "bitflags 2.12.1", + "block", + "cbindgen", + "chrono", + "cocoa 0.26.0", + "cocoa-foundation 0.2.0", + "collections", + "core-foundation 0.10.0", + "core-foundation-sys", + "core-graphics 0.24.0", + "core-text", + "core-video", + "ctor", + "derive_more", + "embed-resource", + "etagere", + "foreign-types", + "futures", + "futures-concurrency", + "getrandom 0.3.4", + "gpui_macros", + "gpui_shared_string", + "gpui_util", + "heapless", + "http_client", + "image", + "inventory", + "itertools 0.14.0", + "log", + "lyon", + "mach2", + "media", + "metal", + "num_cpus", + "objc", + "parking", + "parking_lot", + "pathfinder_geometry", + "pin-project", + "pollster 0.4.0", + "postage", + "profiling", + "rand 0.9.4", + "raw-window-handle", + "refineable", + "regex", + "resvg", + "scheduler", + "schemars", + "seahash", + "serde", + "serde_json", + "slotmap", + "smallvec", + "spin 0.10.0", + "stacksafe", + "strum", + "sum_tree", + "taffy", + "thiserror 2.0.18", + "ttf-parser", + "url", + "usvg", + "util_macros", + "uuid", + "waker-fn", + "web-time", + "windows 0.61.3", + "zed-font-kit", + "zed-scap", +] + +[[package]] +name = "gpui-component" +version = "0.5.2" +source = "git+https://github.com/longbridge/gpui-component?rev=ca85b5588f5feea021f1ee86cb13703dc6f30ec4#ca85b5588f5feea021f1ee86cb13703dc6f30ec4" +dependencies = [ + "aho-corasick", + "anyhow", + "async-channel 2.5.0", + "chrono", + "core-text", + "enum-iterator", + "futures", + "gpui", + "gpui-component-assets", + "gpui-component-macros", + "gpui_macros", + "html5ever", + "instant", + "itertools 0.13.0", + "log", + "lsp-types", + "markdown", + "markup5ever_rcdom", + "notify", + "num-traits", + "once_cell", + "paste", + "regex", + "ropey", + "rust-i18n", + "schemars", + "serde", + "serde_json", + "serde_repr", + "smallvec", + "smol", + "tracing", + "tree-sitter", + "tree-sitter-json", + "unicode-segmentation", + "uuid", + "zed-sum-tree", +] + +[[package]] +name = "gpui-component-assets" +version = "0.5.1" +source = "git+https://github.com/longbridge/gpui-component?rev=ca85b5588f5feea021f1ee86cb13703dc6f30ec4#ca85b5588f5feea021f1ee86cb13703dc6f30ec4" +dependencies = [ + "anyhow", + "gpui", + "log", + "rust-embed", + "wasm-bindgen", + "wasm-bindgen-futures", + "zed-reqwest", +] + +[[package]] +name = "gpui-component-macros" +version = "0.5.1" +source = "git+https://github.com/longbridge/gpui-component?rev=ca85b5588f5feea021f1ee86cb13703dc6f30ec4#ca85b5588f5feea021f1ee86cb13703dc6f30ec4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gpui_linux" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "accesskit", + "accesskit_unix", + "anyhow", + "bytemuck", + "calloop", + "collections", + "futures", + "gpui", + "http_client", + "image", + "itertools 0.14.0", + "libc", + "log", + "oo7", + "parking_lot", + "pathfinder_geometry", + "pollster 0.4.0", + "profiling", + "raw-window-handle", + "smallvec", + "smol", + "strum", + "swash", + "url", + "util", + "uuid", +] + +[[package]] +name = "gpui_macos" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "accesskit", + "accesskit_macos", + "anyhow", + "async-task", + "block", + "cbindgen", + "cocoa 0.26.0", + "collections", + "core-foundation 0.10.0", + "core-foundation-sys", + "core-graphics 0.24.0", + "core-text", + "core-video", + "ctor", + "derive_more", + "dispatch2", + "etagere", + "foreign-types", + "futures", + "gpui", + "image", + "itertools 0.14.0", + "libc", + "log", + "mach2", + "media", + "metal", + "objc", + "objc2-app-kit 0.3.2", + "parking_lot", + "pathfinder_geometry", + "raw-window-handle", + "semver", + "smallvec", + "strum", + "util", + "uuid", + "zed-font-kit", +] + +[[package]] +name = "gpui_macros" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gpui_platform" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "console_error_panic_hook", + "gpui", + "gpui_linux", + "gpui_macos", + "gpui_web", + "gpui_windows", +] + +[[package]] +name = "gpui_shared_string" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "schemars", + "serde", + "smol_str", +] + +[[package]] +name = "gpui_util" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "anyhow", + "log", +] + +[[package]] +name = "gpui_web" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "anyhow", + "console_error_panic_hook", + "futures", + "gpui", + "gpui_wgpu", + "http_client", + "js-sys", + "log", + "parking_lot", + "raw-window-handle", + "smallvec", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm_thread", + "web-sys", + "web-time", +] + +[[package]] +name = "gpui_wgpu" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "anyhow", + "bytemuck", + "collections", + "cosmic-text", + "etagere", + "gpui", + "gpui_util", + "itertools 0.14.0", + "js-sys", + "log", + "parking_lot", + "pollster 0.4.0", + "profiling", + "raw-window-handle", + "smallvec", + "swash", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu", +] + +[[package]] +name = "gpui_windows" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "accesskit", + "accesskit_windows", + "anyhow", + "collections", + "etagere", + "futures", + "gpui", + "image", + "itertools 0.14.0", + "log", + "parking_lot", + "rand 0.9.4", + "raw-window-handle", + "smallvec", + "util", + "uuid", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-numerics 0.2.0", + "windows-registry 0.5.3", +] + +[[package]] +name = "grid" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "harfrust" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da2e5ae821f6e96664977bf974d6d6a2d6682f9ccee23e62ec1d134246845f9" +dependencies = [ + "bitflags 2.12.1", + "bytemuck", + "core_maths", + "read-fonts", + "smallvec", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hexf-parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "html5ever" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "http" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http_client" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "anyhow", + "async-compression", + "async-fs", + "async-tar", + "bytes", + "derive_more", + "futures", + "http", + "http-body", + "log", + "parking_lot", + "serde", + "serde_json", + "serde_urlencoded", + "sha2", + "tempfile", + "url", + "util", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.58.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif 0.14.2", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.1", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core 0.5.1", + "zune-jpeg 0.5.15", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imagesize" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" + +[[package]] +name = "imgref" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "inotify" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "io-surface" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554b8c5d64ec09a3a520fe58e4d48a73e00ff32899cdcbe32a4877afd4968b8e" +dependencies = [ + "cgl", + "core-foundation 0.10.0", + "core-foundation-sys", + "leaky-cow", +] + +[[package]] +name = "ioctl-rs" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d" +dependencies = [ + "libc", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "khronos-egl" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +dependencies = [ + "libc", + "libloading", + "pkg-config", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kqueue" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags 2.12.1", + "libc", +] + +[[package]] +name = "kurbo" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +dependencies = [ + "arrayvec", + "euclid", + "smallvec", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] + +[[package]] +name = "leak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd100e01f1154f2908dfa7d02219aeab25d0b9c7fa955164192e3245255a0c73" + +[[package]] +name = "leaky-cow" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a8225d44241fd324a8af2806ba635fc7c8a7e9a7de4d5cf3ef54e71f5926fc" +dependencies = [ + "leak", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +dependencies = [ + "libc", +] + +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + +[[package]] +name = "link-section" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "014e440054ce8170890229eeef5bcda955305e056ec713de40ed366944483f09" + +[[package]] +name = "linktime-proc-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7b0a3383c2a1002d11349c92c85a666a5fb679e96c79d782cf0dbe557fd6ee" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" +dependencies = [ + "serde_core", + "value-bag", +] + +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lsp-types" +version = "0.97.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071" +dependencies = [ + "bitflags 1.3.2", + "fluent-uri", + "serde", + "serde_json", + "serde_repr", +] + +[[package]] +name = "lyon" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0578bdecb7d6d88987b8b2b1e3a4e2f81df9d0ece1078623324a567904e7b7" +dependencies = [ + "lyon_algorithms", + "lyon_tessellation", +] + +[[package]] +name = "lyon_algorithms" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8575c0d003ae459399623c4def180c63b77f343b1a7fee64f249b349e7699a31" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e43b7e44161571868f5c931d12583592c223c5583eef86b08aa02b7048a3552" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "mach2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" +dependencies = [ + "libc", +] + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markdown" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb" +dependencies = [ + "serde", + "unicode-id", +] + +[[package]] +name = "markup5ever" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "media" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "anyhow", + "bindgen", + "core-foundation 0.10.0", + "core-video", + "ctor", + "foreign-types", + "metal", + "objc", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "memmap2" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "menu" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "gpui", +] + +[[package]] +name = "metal" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" +dependencies = [ + "bitflags 2.12.1", + "block", + "core-graphics-types 0.2.0", + "foreign-types", + "log", + "objc", + "paste", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "miow" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "naga" +version = "29.0.3" +source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.12.1", + "cfg-if", + "cfg_aliases", + "codespan-reporting", + "half", + "hashbrown 0.16.1", + "hexf-parse", + "indexmap", + "libm", + "log", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "spirv", + "thiserror 2.0.18", + "unicode-ident", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset 0.6.5", + "pin-utils", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.12.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.12.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "normpath" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9985ef7269fa99f3b12437bb698381da2428743ab90f20393f399fa14cab21a" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "notify" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009" +dependencies = [ + "bitflags 2.12.1", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.52.0", +] + +[[package]] +name = "notify-types" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174" +dependencies = [ + "instant", +] + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.6", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-bigint-dig" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f9a86e097b0d187ad0e65667c2f58b9254671e86e7dbb78036b16692eae099" +dependencies = [ + "libm", + "num-integer", + "num-iter", + "num-traits", + "once_cell", + "rand 0.9.4", + "serde", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[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-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "libc", + "objc2 0.5.2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.12.1", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.12.1", + "dispatch2", + "objc2 0.6.4", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.12.1", + "objc2 0.6.4", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.12.1", + "block2 0.6.2", + "objc2 0.6.4", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.12.1", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.12.1", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[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.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oo7" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f2bfed90f1618b4b48dcad9307f25e14ae894e2949642c87c351601d62cebd" +dependencies = [ + "aes", + "ashpd 0.13.11", + "async-fs", + "async-io", + "async-lock", + "blocking", + "cbc", + "cipher", + "digest", + "endi", + "futures-lite 2.6.1", + "futures-util", + "getrandom 0.4.2", + "hkdf", + "hmac", + "md-5", + "num", + "num-bigint-dig 0.9.1", + "pbkdf2", + "serde", + "serde_bytes", + "sha2", + "subtle", + "zbus", + "zbus_macros", + "zeroize", + "zvariant", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core 0.6.4", + "sha2", +] + +[[package]] +name = "pageant" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "032d6201d2fb765158455ae0d5a510c016bb6da7232e5040e39e9c8db12b0afc" +dependencies = [ + "bytes", + "delegate", + "futures", + "rand 0.8.6", + "thiserror 1.0.69", + "tokio", + "windows 0.58.0", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4500030c302e4af1d423f36f3b958d1aecb6c04184356ed5a833bf6b60435777" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "perf" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "collections", + "serde", + "serde_json", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared 0.13.1", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.6", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand 2.4.1", + "phf_shared 0.13.1", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator 0.13.1", + "phf_shared 0.13.1", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand 2.4.1", + "futures-io", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "pbkdf2", + "scrypt", + "sha2", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "pkcs5", + "rand_core 0.6.4", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.12.1", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "pollster" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7" + +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "portable-pty" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806ee80c2a03dbe1a9fb9534f8d19e4c0546b790cde8fd1fea9d6390644cb0be" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "downcast-rs", + "filedescriptor", + "lazy_static", + "libc", + "log", + "nix 0.25.1", + "serial", + "shared_library", + "shell-words", + "winapi", + "winreg 0.10.1", +] + +[[package]] +name = "postage" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" +dependencies = [ + "atomic", + "crossbeam-queue", + "futures", + "log", + "parking_lot", + "pin-project", + "pollster 0.2.5", + "static_assertions", + "thiserror 1.0.69", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "presser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.12+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "psm" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" +dependencies = [ + "ar_archive_writer", + "cc", +] + +[[package]] +name = "pxfm" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.2", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash 2.1.2", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "range-alloc" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" + +[[package]] +name = "rangemap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" + +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.4", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.18", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "core_maths", + "font-types", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.12.1", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "refineable" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "derive_refineable", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + +[[package]] +name = "resvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +dependencies = [ + "gif 0.13.3", + "image-webp", + "log", + "pico-args", + "rgb", + "svgtypes", + "tiny-skia", + "usvg", + "zune-jpeg 0.4.21", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd 0.11.1", + "block2 0.6.2", + "dispatch2", + "js-sys", + "log", + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "pollster 0.4.0", + "raw-window-handle", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ropey" +version = "2.0.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4045a00dc327d084a2bbf126976e14125b54f23bd30511d45b842eba76c52d74" +dependencies = [ + "str_indices", +] + +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig 0.8.6", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "sha2", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "russh" +version = "0.49.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b206640a622d63529540fc48036aa39f211b2b71432a451156004f40655cdb4" +dependencies = [ + "aes", + "aes-gcm", + "async-trait", + "bitflags 2.12.1", + "byteorder", + "bytes", + "cbc", + "chacha20", + "ctr", + "curve25519-dalek", + "delegate", + "des", + "digest", + "elliptic-curve", + "flate2", + "futures", + "generic-array", + "hex-literal", + "hmac", + "log", + "num-bigint", + "once_cell", + "p256", + "p384", + "p521", + "poly1305", + "rand 0.8.6", + "rand_core 0.6.4", + "rsa", + "russh-cryptovec", + "russh-keys", + "russh-sftp", + "russh-util", + "sha1", + "sha2", + "signature", + "ssh-encoding", + "ssh-key", + "subtle", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "russh-cryptovec" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d8e7e854e1a87e4be00fa287c98cad23faa064d0464434beaa9f014ec3baa98" +dependencies = [ + "libc", + "ssh-encoding", + "winapi", +] + +[[package]] +name = "russh-keys" +version = "0.49.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788a2439ce385856585346beb37c48e7c9eb5de5f4f00736720a19ffdb3f5bb5" +dependencies = [ + "aes", + "async-trait", + "bcrypt-pbkdf", + "block-padding", + "byteorder", + "bytes", + "cbc", + "ctr", + "data-encoding", + "der", + "digest", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "futures", + "getrandom 0.2.17", + "hmac", + "home", + "inout", + "log", + "md5", + "num-integer", + "p256", + "p384", + "p521", + "pageant", + "pbkdf2", + "pkcs1", + "pkcs5", + "pkcs8", + "rand 0.8.6", + "rand_core 0.6.4", + "rsa", + "russh-cryptovec", + "russh-util", + "sec1", + "serde", + "sha1", + "sha2", + "signature", + "spki", + "ssh-encoding", + "ssh-key", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "typenum", + "zeroize", +] + +[[package]] +name = "russh-sftp" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed8949eca4163c18a8f59ff96d32cf61e9c13b9735e21ef32b3907f4aafa1a9" +dependencies = [ + "bitflags 2.12.1", + "bytes", + "chrono", + "dashmap", + "gloo-timers 0.4.0", + "log", + "serde", + "serde_bytes", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "wasm-bindgen-futures", +] + +[[package]] +name = "russh-util" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c7dd577958c0cefbc8f8a2c05c48c88c42e2fdb760dbe9b96ae31d4de97a1f" +dependencies = [ + "chrono", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "rust-embed" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "shellexpand", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" +dependencies = [ + "globset", + "sha2", + "walkdir", +] + +[[package]] +name = "rust-i18n" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55691a65892c33ee2de49c15ea5600c6f4a70e8eeb8e6c3cd96d2a231d230c40" +dependencies = [ + "globwalk", + "regex", + "rust-i18n-macro", + "rust-i18n-support", + "smallvec", +] + +[[package]] +name = "rust-i18n-macro" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30de488acadcf767d97cd48518a8da8ea9777b1c9a5beca4eab78bbf77d07309" +dependencies = [ + "glob", + "proc-macro2", + "quote", + "rust-i18n-support", + "serde", + "serde_json", + "serde_yaml", + "syn", +] + +[[package]] +name = "rust-i18n-support" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aea0fef8a93c06326b66392c95a115120e609674cb2132d37d276a6b05b545b4" +dependencies = [ + "arc-swap", + "base62", + "globwalk", + "itertools 0.11.0", + "normpath", + "serde", + "serde_json", + "serde_yaml", + "siphasher", + "toml 0.8.23", + "triomphe", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.12.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.12.1", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustix-openpty" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de16c7c59892b870a6336f185dc10943517f1327447096bbb7bb32cd85e2393" +dependencies = [ + "errno", + "libc", + "rustix 1.1.4", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.12.1", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scheduler" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "async-task", + "backtrace", + "chrono", + "flume", + "futures", + "parking_lot", + "rand 0.9.4", + "web-time", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "indexmap", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "screencapturekit" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5eeeb57ac94960cfe5ff4c402be6585ae4c8d29a2cf41b276048c2e849d64e" +dependencies = [ + "screencapturekit-sys", +] + +[[package]] +name = "screencapturekit-sys" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22411b57f7d49e7fe08025198813ee6fd65e1ee5eff4ebc7880c12c82bde4c60" +dependencies = [ + "block", + "dispatch", + "objc", + "objc-foundation", + "objc_id", + "once_cell", +] + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.12.1", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "self_cell" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_fmt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e497af288b3b95d067a23a4f749f2861121ffcb2f6d8379310dcda040c345ed" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_json_lenient" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e033097bf0d2b59a62b42c18ebbb797503839b26afdda2c4e1415cb6c813540" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serial" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86" +dependencies = [ + "serial-core", + "serial-unix", + "serial-windows", +] + +[[package]] +name = "serial-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581" +dependencies = [ + "libc", +] + +[[package]] +name = "serial-unix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7" +dependencies = [ + "ioctl-rs", + "libc", + "serial-core", + "termios", +] + +[[package]] +name = "serial-windows" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162" +dependencies = [ + "libc", + "serial-core", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shared_library" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +dependencies = [ + "lazy_static", + "libc", +] + +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + +[[package]] +name = "shellexpand" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" +dependencies = [ + "dirs", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "skrifa" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +dependencies = [ + "bytemuck", + "read-fonts", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smol" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite 2.6.1", +] + +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "borsh", + "serde_core", +] + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spirv" +version = "0.4.0+sdk-1.4.341.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" +dependencies = [ + "bitflags 2.12.1", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "ssh-cipher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" +dependencies = [ + "aes", + "aes-gcm", + "cbc", + "chacha20", + "cipher", + "ctr", + "poly1305", + "ssh-encoding", + "subtle", +] + +[[package]] +name = "ssh-encoding" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" +dependencies = [ + "base64ct", + "bytes", + "pem-rfc7468", + "sha2", +] + +[[package]] +name = "ssh-key" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3" +dependencies = [ + "bcrypt-pbkdf", + "ed25519-dalek", + "num-bigint-dig 0.8.6", + "p256", + "p384", + "p521", + "rand_core 0.6.4", + "rsa", + "sec1", + "sha2", + "signature", + "ssh-cipher", + "ssh-encoding", + "subtle", + "zeroize", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.60.2", +] + +[[package]] +name = "stacksafe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9c1172965d317e87ddb6d364a040d958b40a1db82b6ef97da26253a8b3d090" +dependencies = [ + "stacker", + "stacksafe-macro", +] + +[[package]] +name = "stacksafe-macro" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172175341049678163e979d9107ca3508046d4d2a7c6682bee46ac541b17db69" +dependencies = [ + "proc-macro-error2", + "quote", + "syn", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str_indices" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08889ec5408683408db66ad89e0e1f93dff55c73a4ccc71c427d5b277ee47e6" + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "sum_tree" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "heapless", + "log", + "rayon", + "tracing", + "ztracing", +] + +[[package]] +name = "sval" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb9efbae90f97301f4d25f3be63dfd99d6b7af9d088228a52ec960d649b2e7d" + +[[package]] +name = "sval_buffer" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff5c0280ea0af40b3a1fd0b532680b5067482ffb81412ee66ec04b1d9952b49a" +dependencies = [ + "sval", + "sval_ref", +] + +[[package]] +name = "sval_dynamic" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b9067f2e68f58e110cf8019a268057e3791cf74b0fdb1b3c7c6e49104f44e6" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ebdbf0e4b175884aa587fcf551c16dabe245ea04235abdd8cbbd160b27ed5a" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e448d9fa216a6c16670b28d624fbbcf5c04e41eb187bd7c52e01222ffa72a12" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021de5b5c26efd544c694cef9b8a9abe8633481bf3be1ea145a18a740818b291" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ef5ffec8bc52ded04ee424ab8d959e25e64fd40a48a16d21f8991ce824c1bb" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b983833a8a2390f89ebcf9b9acd06883017014b4ffd72ee28e0c9de6852039f5" +dependencies = [ + "serde_core", + "sval", + "sval_nested", +] + +[[package]] +name = "svg_fmt" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" + +[[package]] +name = "svgtypes" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +dependencies = [ + "kurbo", + "siphasher", +] + +[[package]] +name = "swash" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "842f3cd369c2ba38966204f983eaa5e54a8e84a7d7159ed36ade2b6c335aae64" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + +[[package]] +name = "sysinfo" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.12.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "taffy" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aea22054047c16c3f34d3ac473a2170be1424b1115b2a3adcf28cfb067c88859" +dependencies = [ + "arrayvec", + "grid", + "serde", + "slotmap", +] + +[[package]] +name = "take-until" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bdb6fa0dfa67b38c1e66b7041ba9dcf23b99d8121907cd31c807a332f7a0bbb" + +[[package]] +name = "tao-core-video-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271450eb289cb4d8d0720c6ce70c72c8c858c93dd61fc625881616752e6b98f6" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "objc", +] + +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr 1.6.1", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand 2.4.1", + "getrandom 0.4.2", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.52.0", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "termios" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" +dependencies = [ + "libc", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg 0.5.15", +] + +[[package]] +name = "tiny-skia" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png 0.17.16", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7e2948f60dbe26b35f2c7fb74ac2854c1fddded0fe9d7548fcc674a246f7615" +dependencies = [ + "either", + "futures-util", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.3", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.3", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tree-sitter" +version = "0.26.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dab76d0b724ba557954125188cf0633a1ca43199ced82d95c7b9c32cc3de1f3" +dependencies = [ + "cc", + "regex", + "regex-syntax", + "serde_json", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-json" +version = "0.24.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d727acca406c0020cffc6cf35516764f36c8e3dc4408e5ebe2cb35a947ec471" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782" + +[[package]] +name = "triomphe" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" +dependencies = [ + "arc-swap", + "serde", + "stable_deref_trait", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset 0.9.1", + "tempfile", + "windows-sys 0.60.2", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + +[[package]] +name = "unicode-id" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-vo" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "usvg" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" +dependencies = [ + "base64", + "data-url", + "flate2", + "fontdb", + "imagesize", + "kurbo", + "log", + "pico-args", + "roxmltree", + "rustybuzz", + "simplecss", + "siphasher", + "strict-num", + "svgtypes", + "tiny-skia-path", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "xmlwriter", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "util" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "anyhow", + "async-fs", + "async_zip", + "collections", + "command-fds", + "dirs", + "dunce", + "futures", + "futures-lite 1.13.0", + "globset", + "gpui_util", + "itertools 0.14.0", + "libc", + "log", + "mach2", + "nix 0.29.0", + "percent-encoding", + "regex", + "rust-embed", + "schemars", + "serde", + "serde_json", + "serde_json_lenient", + "shlex 1.3.0", + "smol", + "take-until", + "tempfile", + "tendril", + "unicase", + "url", + "walkdir", + "which", +] + +[[package]] +name = "util_macros" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "perf", + "quote", + "syn", +] + +[[package]] +name = "uuid" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16530907bfe2999a1773ca5900a65101e092c70f642f25cc23ca0c43573262c5" +dependencies = [ + "erased-serde", + "serde_core", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d00ae130edd690eaa877e4f40605d534790d1cf1d651e7685bd6a144521b251f" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "vte" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" +dependencies = [ + "arrayvec", + "bitflags 2.12.1", + "cursor-icon", + "log", + "memchr", + "serde", +] + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm_thread" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7516db7f32decdadb1c3b8deb1b7d78b9df7606c5cc2f6241737c2ab3a0258e" +dependencies = [ + "futures", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.12.1", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags 2.12.1", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" +dependencies = [ + "bitflags 2.12.1", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml 0.39.4", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "29.0.3" +source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +dependencies = [ + "arrayvec", + "bitflags 2.12.1", + "bytemuck", + "cfg-if", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "js-sys", + "log", + "naga", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "29.0.3" +source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.12.1", + "bytemuck", + "cfg_aliases", + "document-features", + "hashbrown 0.16.1", + "indexmap", + "log", + "naga", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.18", + "wgpu-core-deps-apple", + "wgpu-core-deps-emscripten", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-apple" +version = "29.0.3" +source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-emscripten" +version = "29.0.3" +source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "29.0.3" +source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "29.0.3" +source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash", + "bit-set", + "bitflags 2.12.1", + "block2 0.6.2", + "bytemuck", + "cfg-if", + "cfg_aliases", + "glow", + "glutin_wgl_sys", + "gpu-allocator", + "gpu-descriptor", + "hashbrown 0.16.1", + "js-sys", + "khronos-egl", + "libc", + "libloading", + "log", + "naga", + "ndk-sys", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", + "once_cell", + "ordered-float", + "parking_lot", + "portable-atomic", + "portable-atomic-util", + "profiling", + "range-alloc", + "raw-window-handle", + "raw-window-metal", + "renderdoc-sys", + "smallvec", + "thiserror 2.0.18", + "wasm-bindgen", + "wayland-sys", + "web-sys", + "wgpu-naga-bridge", + "wgpu-types", + "windows 0.62.2", + "windows-core 0.62.2", + "windows-result 0.4.1", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "29.0.3" +source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "29.0.3" +source = "git+https://github.com/zed-industries/wgpu.git?rev=357a0c56e0070480ad9daea5d2eaa83150b79e88#357a0c56e0070480ad9daea5d2eaa83150b79e88" +dependencies = [ + "bitflags 2.12.1", + "bytemuck", + "js-sys", + "log", + "raw-window-handle", + "web-sys", +] + +[[package]] +name = "which" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +dependencies = [ + "either", + "home", + "rustix 0.38.44", + "winsafe", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", +] + +[[package]] +name = "windows-capture" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4df73e95feddb9ec1a7e9c2ca6323b8c97d5eeeff78d28f1eccdf19c882b24" +dependencies = [ + "parking_lot", + "rayon", + "thiserror 2.0.18", + "windows 0.61.3", + "windows-future 0.2.1", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result 0.3.4", + "windows-strings 0.3.1", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[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", +] + +[[package]] +name = "windows-targets" +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_i686_gnullvm 0.52.6", + "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", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[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_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[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_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.12.1", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "workspace-hack" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beffa227304dbaea3ad6a06ac674f9bc83a3dec3b7f63eeb442de37e7cb6bb01" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix 1.1.4", +] + +[[package]] +name = "xcb" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4c580d8205abb0a5cf4eb7e927bd664e425b6c3263f9c5310583da96970cf6" +dependencies = [ + "bitflags 1.3.2", + "libc", + "quick-xml 0.30.0", + "x11", +] + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "xml5ever" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" +dependencies = [ + "log", + "mac", + "markup5ever", +] + +[[package]] +name = "xmlwriter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" + +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8b8abf912b9a29ff112e1671c97c33636903d13a69712037190e6805af4f76" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener 5.4.1", + "futures-core", + "futures-lite 2.6.1", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.4", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.3", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus-lockstep" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" +dependencies = [ + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "zbus-lockstep", + "zbus_xml", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" +dependencies = [ + "serde", + "winnow 1.0.3", + "zvariant", +] + +[[package]] +name = "zbus_xml" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8067892e940ed1727dea64690378601603b31d62dfde019a5335fbb7c0e0ed9" +dependencies = [ + "quick-xml 0.39.4", + "serde", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zed-font-kit" +version = "0.14.1-zed" +source = "git+https://github.com/zed-industries/font-kit?rev=94b0f28166665e8fd2f53ff6d268a14955c82269#94b0f28166665e8fd2f53ff6d268a14955c82269" +dependencies = [ + "bitflags 2.12.1", + "byteorder", + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "core-text", + "dirs", + "dwrote", + "float-ord", + "freetype-sys", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "zed-reqwest" +version = "0.12.15-zed" +source = "git+https://github.com/zed-industries/reqwest.git?rev=c15662463bda39148ba154100dd44d3fba5873a4#c15662463bda39148ba154100dd44d3fba5873a4" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-rustls", + "tokio-socks", + "tokio-util", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry 0.4.0", +] + +[[package]] +name = "zed-scap" +version = "0.0.8-zed" +source = "git+https://github.com/zed-industries/scap?rev=4afea48c3b002197176fb19cd0f9b180dd36eaac#4afea48c3b002197176fb19cd0f9b180dd36eaac" +dependencies = [ + "anyhow", + "cocoa 0.25.0", + "core-graphics-helmer-fork", + "log", + "objc", + "rand 0.8.6", + "screencapturekit", + "screencapturekit-sys", + "sysinfo 0.31.4", + "tao-core-video-sys", + "windows 0.61.3", + "windows-capture", + "x11", + "xcb", +] + +[[package]] +name = "zed-sum-tree" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d490156d0d7311855564d6e1d6dccab992405a0c0e15e1c8ef18920c02177e35" +dependencies = [ + "arrayvec", + "log", + "rayon", + "workspace-hack", +] + +[[package]] +name = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" + +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap", + "memchr", + "thiserror 2.0.18", + "zopfli", +] + +[[package]] +name = "zlog" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "anyhow", + "chrono", + "collections", + "log", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "ztracing" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" +dependencies = [ + "tracing", + "tracing-subscriber", + "zlog", + "ztracing_macro", +] + +[[package]] +name = "ztracing_macro" +version = "0.1.0" +source = "git+https://github.com/zed-industries/zed#dde98fa822c59028475850f242a4827ba770e24a" + +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" +dependencies = [ + "zune-core 0.4.12", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core 0.5.1", +] + +[[package]] +name = "zvariant" +version = "5.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0" +dependencies = [ + "endi", + "enumflags2", + "serde", + "serde_bytes", + "url", + "winnow 1.0.3", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn", + "winnow 1.0.3", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8c4ec06 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "ashell" +version = "0.1.0" +edition = "2024" +authors = ["ashell contributors"] +description = "A GPUI Component based SSH and local terminal client" +license = "GPL-3.0-or-later" +rust-version = "1.85.0" + +[dependencies] +anyhow = "1" +alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "fcf32feacb367b75ec84dd40f041e4fd411d3cc1" } +async-trait = "0.1" +chrono = { version = "0.4", features = ["serde"] } +directories = "5" +flate2 = "1" +gpui = { git = "https://github.com/zed-industries/zed" } +gpui_platform = { git = "https://github.com/zed-industries/zed", features = ["font-kit", "runtime_shaders"] } +gpui-component = { git = "https://github.com/longbridge/gpui-component", rev = "ca85b5588f5feea021f1ee86cb13703dc6f30ec4" } +gpui-component-assets = { git = "https://github.com/longbridge/gpui-component", rev = "ca85b5588f5feea021f1ee86cb13703dc6f30ec4" } +menu = { git = "https://github.com/zed-industries/zed" } +portable-pty = "0.8" +rfd = "0.15" +russh = "0.49" +russh-keys = "0.49" +russh-sftp = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +ssh-key = "0.6" +sysinfo = "0.33" +tar = "0.4" +thiserror = "1" +tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "io-util", "net", "time", "fs"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +uuid = { version = "1", features = ["v4", "serde"] } +walkdir = "2" +zip = { version = "2", default-features = false, features = ["deflate"] } + +[profile.release] +opt-level = 3 +lto = "thin" +codegen-units = 1 +strip = true diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..68f8275 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +[English](README.md) | [中文](README.zh-CN.md) + +# ashell + +`ashell` is a modern, GPUI Component-based desktop terminal client written in Rust. + +This project focuses on providing a high-performance and visually appealing shell workspace by combining local and remote environments with a rich set of built-in features. + +## Features + +The current version provides a fully-featured GPUI-native workspace: + +- **Local & Remote Sessions:** Open local terminal tabs or connect to remote servers via SSH. +- **Advanced SSH Authentication:** Supports both password-based and key-based (file path or inline) SSH connections. +- **Session Management:** Easily save, reopen, edit, and remove your SSH sessions. +- **SFTP Integration:** Built-in SFTP file manager to browse, upload, download, and manage remote files. +- **Robust Terminal Emulator:** Parses terminal output with `alacritty_terminal`, supporting rich ANSI color spans, fast rendering, and complete keyboard input forwarding. +- **System Telemetry:** Real-time visualization of CPU, memory, swap, network, and disk metrics in the left cockpit sidebar. +- **Theming System:** Switch between multiple GPUI Component themes directly from the top toolbar. +- **Embedded Fonts:** Uses embedded Maple Mono NF CN fonts out-of-the-box for excellent CJK character and Nerd Font icon support. + +## Run + +To run the application locally: + +```bash +cargo run --release +``` + +## Package macOS App + +```bash +./scripts/package-macos-app.sh +open target/release/ashell.app +``` + +The packaging script creates a standard `.app` bundle. It does not attach an entitlements file, and after signing, it verifies that `com.apple.security.app-sandbox` is not present (meaning it runs non-sandboxed). + +## License + +This project is licensed under the [GPL-3.0-or-later License](LICENSE). diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..718d460 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,41 @@ +[English](README.md) | [中文](README.zh-CN.md) + +# ashell + +`ashell` 是一款现代化的、基于 GPUI Component 构建的 Rust 桌面终端客户端。 + +该项目旨在通过结合本地和远程环境,并内置丰富的核心功能,提供一个高性能且美观的 Shell 工作区。 + +## 功能特性 + +当前版本提供了一个功能完备的 GPUI 原生工作区: + +- **本地与远程会话**:支持打开本地终端标签页或通过 SSH 连接到远程服务器。 +- **高级 SSH 认证**:支持基于密码和基于密钥(文件路径或内联内容)的 SSH 连接方式。 +- **会话管理**:可以轻松地保存、重新打开、编辑和删除您的 SSH 会话。 +- **SFTP 集成**:内置 SFTP 文件管理器,可以浏览、上传、下载以及管理远程文件。 +- **强大的终端模拟器**:使用 `alacritty_terminal` 解析终端输出,支持丰富的 ANSI 颜色代码、极速渲染和完整的键盘输入转发。 +- **系统遥测**:在左侧边栏实时可视化显示 CPU、内存、Swap、网络和磁盘的使用指标。 +- **主题系统**:支持直接从顶部工具栏切换多种 GPUI Component 颜色主题。 +- **内置字体**:开箱即用,内置 Maple Mono NF CN 字体,提供卓越的中日韩(CJK)字符与 Nerd Font 图标支持。 + +## 运行 + +在本地运行该应用: + +```bash +cargo run --release +``` + +## 打包 macOS 应用 + +```bash +./scripts/package-macos-app.sh +open target/release/ashell.app +``` + +该打包脚本会创建一个标准的 `.app` 应用程序包。它没有附加 entitlements 文件,并且在签名后会验证是否不存在 `com.apple.security.app-sandbox`(这意味着它在非沙盒模式下运行)。 + +## 协议 + +本项目采用 [GPL-3.0-or-later 协议](LICENSE) 开源。 diff --git a/assets/fonts/LICENSE.txt b/assets/fonts/LICENSE.txt new file mode 100644 index 0000000..65ea530 --- /dev/null +++ b/assets/fonts/LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Maple Mono Project Authors (https://github.com/subframe7536/maple-font) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/fonts/MapleMono-NF-CN-Bold.ttf b/assets/fonts/MapleMono-NF-CN-Bold.ttf new file mode 100644 index 0000000..cf9e08b Binary files /dev/null and b/assets/fonts/MapleMono-NF-CN-Bold.ttf differ diff --git a/assets/fonts/MapleMono-NF-CN-Regular.ttf b/assets/fonts/MapleMono-NF-CN-Regular.ttf new file mode 100644 index 0000000..c112ba8 Binary files /dev/null and b/assets/fonts/MapleMono-NF-CN-Regular.ttf differ diff --git a/assets/themes/gruvbox.json b/assets/themes/gruvbox.json new file mode 100644 index 0000000..db446fc --- /dev/null +++ b/assets/themes/gruvbox.json @@ -0,0 +1,302 @@ +{ + "$schema": "https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json", + "name": "Gruvbox", + "author": "Pavel Pertsev", + "url": "https://github.com/morhetz/gruvbox", + "themes": [ + { + "name": "Gruvbox Light", + "mode": "light", + "colors": { + "accent.background": "#e6d19e", + "accent.foreground": "#3c3836", + "background": "#fbf1c7", + "border": "#d5c4a1", + "danger.background": "#fb4934", + "danger.active.background": "#cc241d", + "danger.foreground": "#fbf1c7", + "foreground": "#3c3836", + "input.border": "#d5c4a1", + "link.active.foreground": "#458588", + "link.foreground": "#458588", + "link.hover.foreground": "#458588", + "list.active.background": "#d7992122", + "list.hover.background": "#ebdbb2", + "list.active.border": "#d79921", + "list.even.background": "#f9ecba", + "muted.background": "#d5c4a166", + "muted.foreground": "#928374", + "panel.background": "#ebdbb2", + "popover.background": "#fbf1c7", + "popover.foreground": "#3c3836", + "primary.active.background": "#b7841f", + "primary.background": "#d79921", + "primary.foreground": "#fbf1c7", + "primary.hover.background": "#d79921ee", + "ring": "#d79921", + "scrollbar.background": "#fbf1c700", + "scrollbar.thumb.background": "#d5c4a1", + "secondary.background": "#EBDBB2", + "secondary.active.background": "#d5c4a1", + "secondary.foreground": "#3c3836", + "secondary.hover.background": "#e8d5a6", + "tab.active.background": "#fbf1c7", + "tab.active.foreground": "#3c3836", + "tab.background": "#ebdbb200", + "tab.foreground": "#928374", + "tab_bar.background": "#ebdbb2", + "title_bar.background": "#ebdbb2", + "title_bar.border": "#d5c4a1", + "base.magenta.light": "#D3869B66", + "base.red": "#fb4934", + "base.green": "#67a64f", + "base.yellow": "#b57614", + "base.blue": "#076678", + "base.magenta": "#8f3f71", + "base.cyan": "#427b58" + }, + "highlight": { + "editor.foreground": "#3c3836", + "editor.background": "#fbf1c7", + "editor.active_line.background": "#ebdbb2", + "editor.line_number": "#928374", + "editor.active_line_number": "#3c3836", + "editor.invisible": "#92837466", + "conflict": "#cc241d", + "created": "#79740e", + "deleted": "#9d0006", + "error": "#cc241d", + "hidden": "#928374", + "hint": "#458588", + "ignored": "#928374", + "info": "#458588", + "modified": "#b57614", + "predictive": "#928374", + "renamed": "#b57614", + "success": "#79740e", + "unreachable": "#928374", + "warning": "#d79921", + "syntax": { + "attribute": { + "color": "#b57614" + }, + "boolean": { + "color": "#d79921" + }, + "comment": { + "color": "#928374", + "font_style": "italic" + }, + "comment.doc": { + "color": "#928374", + "font_style": "italic" + }, + "constant": { + "color": "#d79921" + }, + "constructor": { + "color": "#b57614" + }, + "embedded": { + "color": "#3c3836" + }, + "function": { + "color": "#d79921" + }, + "keyword": { + "color": "#cc241d" + }, + "link_text": { + "color": "#458588", + "font_style": "normal" + }, + "link_uri": { + "color": "#076678", + "font_style": "italic" + }, + "number": { + "color": "#cc241d" + }, + "string": { + "color": "#79740e" + }, + "string.escape": { + "color": "#79740e" + }, + "string.regex": { + "color": "#79740e" + }, + "string.special": { + "color": "#d79921" + }, + "string.special.symbol": { + "color": "#d79921" + }, + "tag": { + "color": "#b57614" + }, + "text.literal": { + "color": "#d79921" + }, + "title": { + "color": "#b57614", + "font_weight": 600 + }, + "type": { + "color": "#b57614" + }, + "property": { + "color": "#3c3836" + }, + "variable.special": { + "color": "#cc241d" + } + } + } + }, + { + "name": "Gruvbox Dark", + "mode": "dark", + "colors": { + "accent.background": "#303030", + "accent.foreground": "#ebdbb2", + "background": "#1d2021", + "border": "#3e3936", + "danger.active.background": "#fb4934", + "foreground": "#ebdbb2", + "input.border": "#504945", + "link.active.foreground": "#83a598", + "link.foreground": "#83a598", + "link.hover.foreground": "#83a598", + "list.active.background": "#d7992111", + "list.active.border": "#b17f1b", + "list.even.background": "#282828", + "muted.background": "#3c383655", + "muted.foreground": "#928374", + "panel.background": "#282828", + "popover.background": "#1d2021", + "popover.foreground": "#ebdbb2", + "primary.background": "#d79921", + "primary.foreground": "#282828", + "primary.hover.background": "#fabd2f", + "ring": "#b17f1b", + "scrollbar.background": "#1d202100", + "scrollbar.thumb.background": "#504945", + "secondary.active.background": "#333332", + "secondary.background": "#282828", + "secondary.foreground": "#ebdbb2", + "secondary.hover.background": "#33333299", + "tab.active.background": "#1d2021", + "tab.active.foreground": "#ebdbb2", + "tab.background": "#28282800", + "tab.foreground": "#928374", + "tab_bar.background": "#28282899", + "title_bar.background": "#252525", + "title_bar.border": "#3e3936", + "base.magenta.light": "#D3869B66", + "base.red": "#f06555", + "base.green": "#98971a", + "base.yellow": "#d79921", + "base.blue": "#458588", + "base.magenta": "#b16286", + "base.cyan": "#689d6a" + }, + "highlight": { + "editor.foreground": "#DDDDDD", + "editor.background": "#1d2021", + "editor.active_line.background": "#131313", + "editor.line_number": "#8F8F8F", + "editor.active_line_number": "#DDDDDD", + "editor.invisible": "#92837466", + "conflict": "#f06555", + "created": "#3f72e2", + "deleted": "#9d0006", + "error": "#cc241d", + "hidden": "#9E9E9E", + "hint": "#b283f8", + "ignored": "#928374", + "info": "#458588", + "modified": "#B0A878", + "predictive": "#5D5945", + "renamed": "#b57614", + "success": "#79740e", + "unreachable": "#928374", + "warning": "#d79921", + "syntax": { + "attribute": { + "color": "#be9a52" + }, + "boolean": { + "color": "#E1D797" + }, + "comment": { + "color": "#9E9E9E" + }, + "comment.doc": { + "color": "#9E9E9E" + }, + "constant": { + "color": "#E1D797" + }, + "constructor": { + "color": "#b5af9a" + }, + "embedded": { + "color": "#CACCCA" + }, + "function": { + "color": "#E1D797" + }, + "keyword": { + "color": "#E19773" + }, + "link_text": { + "color": "#A86D3B", + "font_style": "normal" + }, + "link_uri": { + "color": "#6F6D66", + "font_style": "italic" + }, + "number": { + "color": "#E19773" + }, + "string": { + "color": "#76BA53" + }, + "string.escape": { + "color": "#76BA53" + }, + "string.regex": { + "color": "#76BA53" + }, + "string.special": { + "color": "#E1D797" + }, + "string.special.symbol": { + "color": "#E1D797" + }, + "tag": { + "color": "#b5af9a" + }, + "text.literal": { + "color": "#E1D797" + }, + "title": { + "color": "#A76D3B", + "font_weight": 600 + }, + "type": { + "color": "#A86D3B" + }, + "property": { + "color": "#CACCCA" + }, + "variable.special": { + "color": "#E19773" + } + } + } + } + ] +} diff --git a/assets/themes/matrix.json b/assets/themes/matrix.json new file mode 100644 index 0000000..8a592ad --- /dev/null +++ b/assets/themes/matrix.json @@ -0,0 +1,158 @@ +{ + "$schema": "https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json", + "name": "Matrix", + "author": "iruzo", + "url": "https://github.com/iruzo/matrix-nvim", + "themes": [ + { + "name": "Matrix", + "mode": "dark", + "radius": 0, + "radius.lg": 0, + "colors": { + "accent.background": "#002d00", + "accent.foreground": "#00FF00", + "background": "#020D02", + "border": "#12410E", + "window_border": "#156B12", + "ring": "#00FF00", + "danger.background": "#FF0000", + "danger.active.background": "#FF0000", + "danger.foreground": "#0D0208", + "danger.hover.background": "#FF0000", + "foreground": "#88FF88", + "input.border": "#003200", + "link.active.foreground": "#00FF00", + "link.foreground": "#00FF00", + "link.hover.foreground": "#00FF00", + "list.active.background": "#00FF0011", + "list.active.border": "#00FF00", + "list.even.background": "#00190066", + "muted.background": "#001900", + "muted.foreground": "#007700", + "panel.background": "#001900", + "popover.background": "#020D02", + "popover.foreground": "#00FF00", + "primary.active.background": "#00FF0099", + "primary.background": "#00FF00", + "primary.foreground": "#0D0208", + "primary.hover.background": "#00FF00AA", + "scrollbar.background": "#0D020800", + "scrollbar.thumb.background": "#003d00", + "secondary.active.background": "#001B00", + "secondary.background": "#002900", + "secondary.foreground": "#3fd43a", + "secondary.hover.background": "#003300", + "tab.active.foreground": "#00FF00", + "tab.background": "#0D020800", + "tab.foreground": "#88FF88", + "title_bar.background": "#020d02", + "title_bar.border": "#12410E", + "base.red": "#FF0000", + "base.green": "#00FF00", + "base.yellow": "#ffea00", + "base.blue": "#0000ff", + "base.magenta": "#FF00FF", + "base.cyan": "#00ffd5" + }, + "highlight": { + "editor.foreground": "#88FF88", + "editor.background": "#020d02", + "editor.active_line.background": "#001900", + "editor.line_number": "#007700", + "editor.active_line_number": "#00FF00", + "editor.invisible": "#00770066", + "conflict": "#FF0000", + "created": "#82d967", + "deleted": "#FF0000", + "error": "#FF0000", + "hidden": "#007700", + "hint": "#3fd43a", + "ignored": "#007700", + "info": "#3fd43a", + "modified": "#82d967", + "predictive": "#007700", + "renamed": "#82d967", + "success": "#82d967", + "unreachable": "#007700", + "warning": "#ffd700", + "syntax": { + "attribute": { + "color": "#82d967" + }, + "boolean": { + "color": "#ffd700" + }, + "comment": { + "color": "#007700", + "font_style": "italic" + }, + "comment.doc": { + "color": "#007700", + "font_style": "italic" + }, + "constant": { + "color": "#ffd700" + }, + "constructor": { + "color": "#82d967" + }, + "embedded": { + "color": "#88FF88" + }, + "function": { + "color": "#ffd700" + }, + "keyword": { + "color": "#FF0000" + }, + "link_text": { + "color": "#3fd43a", + "font_style": "normal" + }, + "link_uri": { + "color": "#00FF00", + "font_style": "italic" + }, + "number": { + "color": "#FF0000" + }, + "string": { + "color": "#82d967" + }, + "string.escape": { + "color": "#82d967" + }, + "string.regex": { + "color": "#82d967" + }, + "string.special": { + "color": "#ffd700" + }, + "string.special.symbol": { + "color": "#ffd700" + }, + "tag": { + "color": "#82d967" + }, + "text.literal": { + "color": "#ffd700" + }, + "title": { + "color": "#82d967", + "font_weight": 600 + }, + "type": { + "color": "#82d967" + }, + "property": { + "color": "#88FF88" + }, + "variable.special": { + "color": "#FF0000" + } + } + } + } + ] +} diff --git a/assets/themes/solarized.json b/assets/themes/solarized.json new file mode 100644 index 0000000..9d971b9 --- /dev/null +++ b/assets/themes/solarized.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json", + "name": "Solarized", + "author": "Ethan Schoonover", + "url": "https://ethanschoonover.com/solarized", + "themes": [ + { + "name": "Solarized Light", + "mode": "light", + "colors": { + "accent.background": "#ebe4ce", + "accent.foreground": "#073642", + "background": "#FDF6E3", + "border": "#DCD4BC", + "foreground": "#586E75", + "input.border": "#DCD4BC", + "link.foreground": "#587573", + "list.active.background": "#586E75", + "list.active.border": "#586E75", + "list.even.background": "#EEE8D599", + "muted.background": "#eee8d5", + "muted.foreground": "#93a1a1", + "panel.background": "#fdf6e3", + "popover.background": "#fdf6e3", + "popover.foreground": "#073642", + "primary.background": "#586E75", + "primary.foreground": "#fdf6e3", + "scrollbar.background": "#EEE8D500", + "scrollbar.thumb.background": "#d5cbaf", + "secondary.background": "#EEE8D5", + "secondary.active.background": "#DCD4BC", + "secondary.foreground": "#073642", + "secondary.hover.background": "#DCD4BC88", + "tab.active.background": "#fdf6e3", + "tab.active.foreground": "#073642", + "tab.background": "#fdf6e300", + "tab.foreground": "#657b83", + "tab_bar.background": "#eee8d5", + "title_bar.background": "#f4ecd7", + "title_bar.border": "#dcd4bc", + "chart.grid": "#ebe5d4", + "base.red": "#755858", + "base.green": "#717558", + "base.yellow": "#756e58", + "base.blue": "#586975", + "base.magenta": "#755866", + "base.cyan": "#587573" + }, + "highlight": { + "editor.foreground": "#586E75", + "editor.background": "#FDF6E3", + "editor.active_line.background": "#EEE8D5", + "editor.line_number": "#93A1A1", + "editor.active_line_number": "#073642", + "editor.invisible": "#93A1A166", + "conflict": "#DC322F", + "created": "#859900", + "deleted": "#DC322F", + "error": "#DC322F", + "hidden": "#93A1A1", + "hint": "#2AA198", + "ignored": "#93A1A1", + "info": "#268BD2", + "modified": "#B58900", + "predictive": "#586E75", + "renamed": "#2AA198", + "success": "#859900", + "unreachable": "#DC322F", + "warning": "#B58900", + "syntax": { + "attribute": { + "color": "#B58900" + }, + "boolean": { + "color": "#CB4B16" + }, + "comment": { + "color": "#93A1A1", + "font_style": "italic" + }, + "comment.doc": { + "color": "#93A1A1", + "font_style": "italic" + }, + "constant": { + "color": "#268BD2" + }, + "constructor": { + "color": "#859900" + }, + "embedded": { + "color": "#2AA198" + }, + "function": { + "color": "#268BD2" + }, + "keyword": { + "color": "#DC322F" + }, + "link_text": { + "color": "#268BD2", + "font_style": "underline" + }, + "link_uri": { + "color": "#2AA198", + "font_style": "italic" + }, + "number": { + "color": "#D33682" + }, + "string": { + "color": "#859900" + }, + "string.escape": { + "color": "#2AA198" + }, + "string.regex": { + "color": "#D33682" + }, + "string.special": { + "color": "#B58900" + }, + "string.special.symbol": { + "color": "#B58900" + }, + "tag": { + "color": "#268BD2" + }, + "text.literal": { + "color": "#586E75" + }, + "title": { + "color": "#073642", + "font_weight": 600 + }, + "type": { + "color": "#2AA198" + }, + "property": { + "color": "#586E75" + }, + "variable.special": { + "color": "#DC322F" + } + } + } + }, + { + "name": "Solarized Dark", + "author": "Ethan Schoonover", + "url": "https://ethanschoonover.com/solarized", + "mode": "dark", + "colors": { + "accent.background": "#073945", + "accent.foreground": "#fdf6e3", + "background": "#002B36", + "border": "#103a44", + "foreground": "#fdf6e3", + "input.border": "#083e4c", + "list.active.background": "#0d667d25", + "list.active.border": "#0d667d", + "list.even.background": "#15333A99", + "muted.background": "#073642", + "muted.foreground": "#839496", + "panel.background": "#002b36", + "popover.background": "#002b36", + "popover.foreground": "#fdf6e3", + "primary.background": "#0d667d", + "primary.foreground": "#fdf6e3", + "scrollbar.background": "#002b3600", + "scrollbar.thumb.background": "#586e75", + "secondary.background": "#073642", + "secondary.active.background": "#083e4b", + "secondary.foreground": "#fdf6e3", + "secondary.hover.background": "#073945", + "tab.active.background": "#002b36", + "tab.active.foreground": "#fdf6e3", + "tab.foreground": "#93a1a1", + "tab_bar.background": "#07364266", + "title_bar.background": "#09323d", + "title_bar.border": "#16404b", + "base.red": "#7d2f0d", + "base.green": "#0d7d3f", + "base.yellow": "#7d650d", + "base.blue": "#0d507d", + "base.magenta": "#7d0d43", + "base.cyan": "#0d7d70" + }, + "highlight": { + "editor.foreground": "#DDDDDD", + "editor.background": "#002B36", + "editor.active_line.background": "#073642", + "editor.line_number": "#8F8F8F", + "editor.active_line_number": "#DDDDDD", + "editor.invisible": "#93A1A166", + "conflict": "#D2602D", + "created": "#3f72e2", + "created.background": "#0C4619", + "deleted.background": "#46190C", + "error.background": "#46190C", + "error.border": "#802207", + "hidden": "#9E9E9E", + "hint": "#b283f8", + "hint.background": "#250c4b", + "hint.border": "#3f0891", + "info.background": "#0C194D", + "info.border": "#082190", + "modified": "#B0A878", + "modified.background": "#3A310E", + "predictive": "#5D5945", + "success.background": "#0C4619", + "warning.background": "#3A310E", + "warning.border": "#7B6508", + "syntax": { + "attribute": { + "color": "#be9a52" + }, + "boolean": { + "color": "#E1D797" + }, + "comment": { + "color": "#9E9E9E" + }, + "comment.doc": { + "color": "#9E9E9E" + }, + "constant": { + "color": "#E1D797" + }, + "constructor": { + "color": "#b5af9a" + }, + "embedded": { + "color": "#CACCCA" + }, + "function": { + "color": "#E1D797" + }, + "keyword": { + "color": "#E19773" + }, + "link_text": { + "color": "#A86D3B", + "font_style": "normal" + }, + "link_uri": { + "color": "#6F6D66", + "font_style": "italic" + }, + "number": { + "color": "#E19773" + }, + "string": { + "color": "#76BA53" + }, + "string.escape": { + "color": "#76BA53" + }, + "string.regex": { + "color": "#76BA53" + }, + "string.special": { + "color": "#E1D797" + }, + "string.special.symbol": { + "color": "#E1D797" + }, + "tag": { + "color": "#b5af9a" + }, + "text.literal": { + "color": "#E1D797" + }, + "title": { + "color": "#A76D3B", + "font_weight": 600 + }, + "type": { + "color": "#A86D3B" + }, + "property": { + "color": "#CACCCA" + }, + "variable.special": { + "color": "#E19773" + } + } + } + } + ] +} diff --git a/assets/themes/tokyonight.json b/assets/themes/tokyonight.json new file mode 100644 index 0000000..d014730 --- /dev/null +++ b/assets/themes/tokyonight.json @@ -0,0 +1,421 @@ +{ + "name": "Tokyo", + "author": "Folke Lemaitre", + "url": "https://github.com/folke/tokyonight.nvim", + "$schema": "https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json", + "themes": [ + { + "name": "Tokyo Night", + "mode": "dark", + "colors": { + "accent.background": "#2C3045", + "accent.foreground": "#c0caf5", + "background": "#1a1b26", + "border": "#292e42", + "window_border": "#3d4462", + "ring": "#7aa2f7", + "foreground": "#c0caf5", + "link.active.foreground": "#7aa2f722", + "link.foreground": "#7aa2f7", + "link.hover.foreground": "#7aa2f7", + "list.active.background": "#7aa2f722", + "list.active.border": "#7aa2f7", + "list.even.background": "#2C304599", + "muted.background": "#2C304544", + "muted.foreground": "#565f89", + "panel.background": "#292e42", + "popover.background": "#1a1b26", + "popover.foreground": "#c0caf5", + "primary.background": "#7aa2f7", + "primary.foreground": "#1a1b26", + "scrollbar.background": "#1a1b2600", + "scrollbar.thumb.background": "#414868", + "secondary.active.background": "#292e42", + "secondary.background": "#292e42", + "secondary.foreground": "#C0CAF5", + "secondary.hover.background": "#2d3349", + "title_bar.background": "#161720", + "title_bar.border": "#292e42", + "chart.grid": "#222332", + "base.red": "#f7768e", + "base.green": "#9ece6a", + "base.yellow": "#e0af68", + "base.blue": "#7aa2f7", + "base.magenta": "#565f89", + "base.cyan": "#7dcfff" + }, + "highlight": { + "editor.foreground": "#c0caf5", + "editor.background": "#1a1b26", + "editor.active_line.background": "#292e42", + "editor.line_number": "#565f89", + "editor.active_line_number": "#c0caf5", + "editor.invisible": "#565f8966", + "conflict": "#f7768e", + "created": "#9ece6a", + "deleted": "#f7768e", + "error": "#f7768e", + "hidden": "#565f89", + "hint": "#7dcfff", + "ignored": "#565f89", + "info": "#7aa2f7", + "modified": "#e0af68", + "predictive": "#565f89", + "renamed": "#7aa2f7", + "success": "#9ece6a", + "unreachable": "#565f89", + "warning": "#e0af68", + "syntax": { + "attribute": { + "color": "#e0af68" + }, + "boolean": { + "color": "#9ece6a" + }, + "comment": { + "color": "#565f89", + "font_style": "italic" + }, + "comment.doc": { + "color": "#565f89", + "font_style": "italic" + }, + "constant": { + "color": "#7aa2f7" + }, + "constructor": { + "color": "#e0af68" + }, + "embedded": { + "color": "#c0caf5" + }, + "function": { + "color": "#7aa2f7" + }, + "keyword": { + "color": "#f7768e" + }, + "link_text": { + "color": "#7dcfff", + "font_style": "underline" + }, + "link_uri": { + "color": "#7aa2f7", + "font_style": "italic" + }, + "number": { + "color": "#e0af68" + }, + "string": { + "color": "#9ece6a" + }, + "string.escape": { + "color": "#7dcfff" + }, + "string.regex": { + "color": "#9ece6a" + }, + "string.special": { + "color": "#e0af68" + }, + "string.special.symbol": { + "color": "#e0af68" + }, + "tag": { + "color": "#f7768e" + }, + "text.literal": { + "color": "#c0caf5" + }, + "title": { + "color": "#7aa2f7", + "font_weight": 600 + }, + "type": { + "color": "#7dcfff" + }, + "property": { + "color": "#c0caf5" + }, + "variable.special": { + "color": "#f7768e" + } + } + } + }, + { + "name": "Tokyo Storm", + "mode": "dark", + "colors": { + "accent.background": "#414868", + "accent.foreground": "#c0caf5", + "background": "#24283b", + "border": "#3d4462", + "window_border": "#3d4462", + "ring": "#7aa2f7", + "foreground": "#c0caf5", + "input.border": "#414868", + "link.active.foreground": "#7aa2f7", + "link.foreground": "#7aa2f7", + "link.hover.foreground": "#7aa2f7", + "list.active.background": "#7aa2f711", + "list.active.border": "#7aa2f7", + "list.even.background": "#41486844", + "muted.background": "#292e42", + "muted.foreground": "#565f89", + "panel.background": "#292e42", + "popover.background": "#24283b", + "popover.foreground": "#c0caf5", + "primary.background": "#7aa2f7", + "primary.foreground": "#24283b", + "scrollbar.background": "#24283b00", + "scrollbar.thumb.background": "#414868", + "secondary.active.background": "#323750", + "secondary.background": "#292e42", + "secondary.foreground": "#c0caf5", + "secondary.hover.background": "#32375099", + "title_bar.background": "#202435", + "title_bar.border": "#3d4462", + "base.red": "#f7768e", + "base.green": "#9ece6a", + "base.yellow": "#e0af68", + "base.blue": "#7aa2f7", + "base.magenta": "#b283f8", + "base.cyan": "#7dcfff" + }, + "highlight": { + "editor.foreground": "#B0B9E2", + "editor.background": "#24283B", + "editor.active_line.background": "#292E42", + "editor.line_number": "#363C58", + "editor.active_line_number": "#B0B9E2", + "editor.invisible": "#565f8966", + "conflict": "#D2602D", + "created": "#3f72e2", + "deleted": "#f7768e", + "error": "#f7768e", + "hidden": "#9E9E9E", + "hint": "#b283f8", + "modified": "#B0A878", + "predictive": "#5D5945", + "success": "#9ece6a", + "warning": "#e0af68", + "syntax": { + "attribute": { + "color": "#be9a52" + }, + "boolean": { + "color": "#E1D797" + }, + "comment": { + "color": "#9E9E9E" + }, + "comment.doc": { + "color": "#9E9E9E" + }, + "constant": { + "color": "#E1D797" + }, + "constructor": { + "color": "#b5af9a" + }, + "embedded": { + "color": "#CACCCA" + }, + "function": { + "color": "#E1D797" + }, + "keyword": { + "color": "#E19773" + }, + "link_text": { + "color": "#A86D3B", + "font_style": "normal" + }, + "link_uri": { + "color": "#6F6D66", + "font_style": "italic" + }, + "number": { + "color": "#E19773" + }, + "string": { + "color": "#76BA53" + }, + "string.escape": { + "color": "#76BA53" + }, + "string.regex": { + "color": "#76BA53" + }, + "string.special": { + "color": "#E1D797" + }, + "string.special.symbol": { + "color": "#E1D797" + }, + "tag": { + "color": "#b5af9a" + }, + "text.literal": { + "color": "#E1D797" + }, + "title": { + "color": "#A76D3B", + "font_weight": 600 + }, + "type": { + "color": "#A86D3B" + }, + "property": { + "color": "#CACCCA" + }, + "variable.special": { + "color": "#E19773" + } + } + } + }, + { + "name": "Tokyo Moon", + "mode": "dark", + "colors": { + "accent.background": "#32364E", + "accent.foreground": "#c0caf5", + "background": "#222436", + "border": "#323752", + "window_border": "#404668", + "ring": "#82aaff", + "foreground": "#c0caf5", + "input.border": "#444b6a", + "link.active.foreground": "#82aaff", + "link.foreground": "#82aaff", + "link.hover.foreground": "#82aaff", + "list.active.background": "#82aaff22", + "list.active.border": "#82aaff", + "list.background": "#222436", + "list.even.background": "#282a40", + "muted.background": "#2d3149", + "muted.foreground": "#6e738d", + "panel.background": "#2d3149", + "popover.background": "#222436", + "popover.foreground": "#c0caf5", + "primary.background": "#82aaff", + "primary.foreground": "#222436", + "scrollbar.background": "#22243600", + "scrollbar.thumb.background": "#444b6a", + "secondary.active.background": "#2f334c", + "secondary.background": "#2d3149", + "secondary.foreground": "#8c94b5", + "secondary.hover.background": "#31354f", + "table.row.border": "#2d3149", + "title_bar.background": "#1e2030", + "title_bar.border": "#323752", + "base.red": "#ff757f", + "base.green": "#c3e88d", + "base.yellow": "#ffc777", + "base.blue": "#82aaff", + "base.magenta": "#6e738d", + "base.cyan": "#86e1fc" + }, + "highlight": { + "editor.foreground": "#c0caf5", + "editor.background": "#222436", + "editor.active_line.background": "#2d3149", + "editor.line_number": "#6e738d", + "editor.active_line_number": "#c0caf5", + "editor.invisible": "#565f8966", + "conflict": "#ed8796", + "created": "#c3e88d", + "deleted": "#ed8796", + "error": "#ed8796", + "hidden": "#6e738d", + "hint": "#86e1fc", + "ignored": "#6e738d", + "info": "#82aaff", + "modified": "#ffc777", + "predictive": "#6e738d", + "renamed": "#82aaff", + "success": "#c3e88d", + "unreachable": "#6e738d", + "warning": "#ffc777", + "syntax": { + "attribute": { + "color": "#ffc777" + }, + "boolean": { + "color": "#c3e88d" + }, + "comment": { + "color": "#6e738d", + "font_style": "italic" + }, + "comment.doc": { + "color": "#6e738d", + "font_style": "italic" + }, + "constant": { + "color": "#82aaff" + }, + "constructor": { + "color": "#ffc777" + }, + "embedded": { + "color": "#c0caf5" + }, + "function": { + "color": "#82aaff" + }, + "keyword": { + "color": "#ed8796" + }, + "link_text": { + "color": "#86e1fc", + "font_style": "underline" + }, + "link_uri": { + "color": "#82aaff", + "font_style": "italic" + }, + "number": { + "color": "#ffc777" + }, + "string": { + "color": "#c3e88d" + }, + "string.escape": { + "color": "#86e1fc" + }, + "string.regex": { + "color": "#c3e88d" + }, + "string.special": { + "color": "#ffc777" + }, + "string.special.symbol": { + "color": "#ffc777" + }, + "tag": { + "color": "#ed8796" + }, + "text.literal": { + "color": "#c0caf5" + }, + "title": { + "color": "#82aaff", + "font_weight": 600 + }, + "type": { + "color": "#86e1fc" + }, + "property": { + "color": "#c0caf5" + }, + "variable.special": { + "color": "#ed8796" + } + } + } + } + ] +} diff --git a/scripts/package-macos-app.sh b/scripts/package-macos-app.sh new file mode 100755 index 0000000..50648ab --- /dev/null +++ b/scripts/package-macos-app.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +APP_NAME="ashell" +BUNDLE_ID="dev.ashell.app" +APP_DIR="$ROOT_DIR/target/release/${APP_NAME}.app" +CONTENTS_DIR="$APP_DIR/Contents" +MACOS_DIR="$CONTENTS_DIR/MacOS" +RESOURCES_DIR="$CONTENTS_DIR/Resources" +SIGN_IDENTITY="${SIGN_IDENTITY:--}" + +cd "$ROOT_DIR" +cargo build --release + +rm -rf "$APP_DIR" +mkdir -p "$MACOS_DIR" "$RESOURCES_DIR" +cp "$ROOT_DIR/target/release/$APP_NAME" "$MACOS_DIR/$APP_NAME" + +cat > "$CONTENTS_DIR/Info.plist" < + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $APP_NAME + CFBundleIdentifier + $BUNDLE_ID + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $APP_NAME + CFBundlePackageType + APPL + CFBundleShortVersionString + 0.1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + 12.0 + NSHighResolutionCapable + + + +EOF + +printf 'APPL????' > "$CONTENTS_DIR/PkgInfo" + +if command -v codesign >/dev/null 2>&1; then + # Important: do not pass an entitlements file here. + # A sandboxed macOS app carries the `com.apple.security.app-sandbox` entitlement, + # which would prevent the file access behavior this app needs. + codesign --force --deep --sign "$SIGN_IDENTITY" "$APP_DIR" >/dev/null + + ENTITLEMENTS_XML="$(codesign -d --entitlements :- "$APP_DIR" 2>/dev/null || true)" + if printf '%s' "$ENTITLEMENTS_XML" | grep -q "com.apple.security.app-sandbox"; then + echo "error: app bundle is sandboxed; remove app sandbox entitlements before packaging" >&2 + exit 1 + fi +fi + +echo "$APP_DIR" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..d29f6e4 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,193 @@ +use std::{fs, path::PathBuf}; + +use anyhow::{Context, Result}; +use directories::BaseDirs; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum AuthMethod { + Password, + Key, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Session { + pub id: String, + pub name: String, + pub host: String, + pub port: u16, + pub user: String, + pub auth: AuthMethod, + #[serde(default)] + pub password: String, + #[serde(default)] + pub private_key_path: String, + #[serde(default)] + pub private_key_inline: String, + #[serde(default)] + pub last_used: Option, +} + +impl Session { + pub fn password(host: String, port: u16, user: String, password: String) -> Self { + let name = format!("{user}@{host}"); + Self { + id: Uuid::new_v4().to_string(), + name, + host, + port, + user, + auth: AuthMethod::Password, + password, + private_key_path: String::new(), + private_key_inline: String::new(), + last_used: None, + } + } + + pub fn key( + host: String, + port: u16, + user: String, + private_key_path: String, + private_key_inline: String, + ) -> Self { + let name = format!("{user}@{host}"); + Self { + id: Uuid::new_v4().to_string(), + name, + host, + port, + user, + auth: AuthMethod::Key, + password: String::new(), + private_key_path, + private_key_inline, + last_used: None, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct ConfigFile { + #[serde(default)] + pub follow_system_theme: bool, + #[serde(default)] + pub light_theme_name: String, + #[serde(default)] + pub dark_theme_name: String, + #[serde(default = "default_terminal_font_size")] + pub terminal_font_size: f32, + #[serde(default)] + pub sessions: Vec, +} + +fn default_terminal_font_size() -> f32 { + 13.0 +} + +pub struct ConfigStore { + path: PathBuf, + cache: ConfigFile, +} + +impl ConfigStore { + pub fn load() -> Result { + let path = Self::config_path()?; + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .with_context(|| format!("failed to create config dir {}", parent.display()))?; + } + + let cache = if path.exists() { + let raw = fs::read_to_string(&path) + .with_context(|| format!("failed to read {}", path.display()))?; + serde_json::from_str::(&raw).unwrap_or_default() + } else { + ConfigFile::default() + }; + + Ok(Self { path, cache }) + } + + pub fn in_memory() -> Self { + Self { + path: PathBuf::new(), + cache: ConfigFile::default(), + } + } + + fn config_path() -> Result { + let dirs = BaseDirs::new().context("could not determine user home directory")?; + Ok(dirs + .home_dir() + .join(".config") + .join("ashell") + .join("sessions.json")) + } + + pub fn sessions(&self) -> &[Session] { + &self.cache.sessions + } + + pub fn follow_system_theme(&self) -> bool { + self.cache.follow_system_theme + } + + pub fn light_theme_name(&self) -> &str { + &self.cache.light_theme_name + } + + pub fn dark_theme_name(&self) -> &str { + &self.cache.dark_theme_name + } + + pub fn terminal_font_size(&self) -> f32 { + if self.cache.terminal_font_size <= 0.0 { + default_terminal_font_size() + } else { + self.cache.terminal_font_size + } + } + + pub fn set_theme_preferences( + &mut self, + follow_system_theme: bool, + light_theme_name: impl Into, + dark_theme_name: impl Into, + ) { + self.cache.follow_system_theme = follow_system_theme; + self.cache.light_theme_name = light_theme_name.into(); + self.cache.dark_theme_name = dark_theme_name.into(); + } + + pub fn set_terminal_font_size(&mut self, terminal_font_size: f32) { + self.cache.terminal_font_size = terminal_font_size.max(10.0); + } + + pub fn get(&self, id: &str) -> Option<&Session> { + self.cache.sessions.iter().find(|s| s.id == id) + } + + pub fn upsert(&mut self, session: Session) { + if let Some(existing) = self.cache.sessions.iter_mut().find(|s| s.id == session.id) { + *existing = session; + } else { + self.cache.sessions.push(session); + } + } + + pub fn remove(&mut self, id: &str) { + self.cache.sessions.retain(|s| s.id != id); + } + + pub fn save(&self) -> Result<()> { + if self.path.as_os_str().is_empty() { + return Ok(()); + } + let raw = serde_json::to_string_pretty(&self.cache)?; + fs::write(&self.path, raw).with_context(|| format!("failed to write {}", self.path.display())) + } +} diff --git a/src/local_terminal.rs b/src/local_terminal.rs new file mode 100644 index 0000000..13db406 --- /dev/null +++ b/src/local_terminal.rs @@ -0,0 +1,126 @@ +use std::{ + io::{Read, Write}, + sync::mpsc::{self, Sender}, + thread, +}; + +use anyhow::{Context, Result}; +use portable_pty::{native_pty_system, CommandBuilder, PtySize}; + +use crate::terminal::{BackendCommand, BackendEvent, BackendTx}; + +pub fn spawn_local_terminal( + tab_id: String, + cols: u16, + rows: u16, + events: Sender, +) -> Result { + let pty_system = native_pty_system(); + let pair = pty_system + .openpty(PtySize { + rows, + cols, + pixel_width: 0, + pixel_height: 0, + }) + .context("open local PTY")?; + + let shell = std::env::var("SHELL").unwrap_or_else(|_| { + if cfg!(windows) { + "powershell.exe".into() + } else { + "/bin/zsh".into() + } + }); + + let mut cmd = CommandBuilder::new(&shell); + cmd.env("TERM", std::env::var("TERM").unwrap_or_else(|_| "xterm-256color".into())); + cmd.env( + "COLORTERM", + std::env::var("COLORTERM").unwrap_or_else(|_| "truecolor".into()), + ); + cmd.env("TERM_PROGRAM", "ashell"); + if let Ok(path) = std::env::var("PATH") { + cmd.env("PATH", path); + } + if let Ok(lang) = std::env::var("LANG") { + cmd.env("LANG", lang); + } else { + cmd.env("LANG", "en_US.UTF-8"); + } + if let Ok(home) = std::env::var("HOME") { + cmd.env("HOME", home); + } + cmd.env("SHELL", shell); + let mut child = pair.slave.spawn_command(cmd).context("spawn local shell")?; + drop(pair.slave); + + let master = pair.master; + let mut reader = master.try_clone_reader().context("clone PTY reader")?; + let mut writer = master.take_writer().context("take PTY writer")?; + let (cmd_tx, cmd_rx) = mpsc::channel::(); + + let read_tab = tab_id.clone(); + let read_events = events.clone(); + thread::spawn(move || { + let mut buf = [0u8; 8192]; + loop { + match reader.read(&mut buf) { + Ok(0) => break, + Ok(n) => { + let _ = read_events.send(BackendEvent::Output { + tab_id: read_tab.clone(), + bytes: buf[..n].to_vec(), + }); + } + Err(err) => { + let _ = read_events.send(BackendEvent::Closed { + tab_id: read_tab.clone(), + reason: format!("local read error: {err}"), + }); + return; + } + } + } + let _ = read_events.send(BackendEvent::Closed { + tab_id: read_tab, + reason: "local shell closed".into(), + }); + }); + + let write_tab = tab_id.clone(); + let write_events = events.clone(); + thread::spawn(move || { + while let Ok(command) = cmd_rx.recv() { + match command { + BackendCommand::Input(bytes) => { + if let Err(err) = writer.write_all(&bytes) { + let _ = write_events.send(BackendEvent::Closed { + tab_id: write_tab.clone(), + reason: format!("local write error: {err}"), + }); + break; + } + let _ = writer.flush(); + } + BackendCommand::Resize { cols, rows } => { + let _ = master.resize(PtySize { + rows, + cols, + pixel_width: 0, + pixel_height: 0, + }); + } + BackendCommand::Close => break, + } + } + let _ = child.kill(); + }); + + let _ = events.send(BackendEvent::Status { + tab_id, + text: "local shell ready".into(), + }); + + Ok(BackendTx::Local(cmd_tx)) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..85138f5 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3278 @@ +use std::{ + borrow::Cow, + cell::{Cell, RefCell}, + collections::HashMap, + ops::Range, + process::Command, + rc::Rc, + sync::mpsc, + time::{Duration, Instant}, +}; + +use alacritty_terminal::index::Side; +use alacritty_terminal::selection::SelectionType; +use anyhow::{Context as _, Result}; +use gpui::{ + div, point, prelude::FluentBuilder as _, px, size, uniform_list, Anchor, App, AppContext as _, + Bounds, ClipboardItem, Context, Entity, FocusHandle, Focusable as _, FontWeight, Hsla, + InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent, + MouseMoveEvent, MouseUpEvent, ParentElement as _, PathPromptOptions, Pixels, Point, QuitMode, + Render, ScrollDelta, ScrollWheelEvent, SharedString, Size, Styled as _, + UniformListScrollHandle, Window, WindowOptions, +}; +use gpui_component::{ + button::{Button, ButtonVariants as _}, + checkbox::Checkbox, + dialog::Dialog, + h_flex, + input::{Input, InputEvent, InputState}, + menu::{ContextMenuExt as _, DropdownMenu as _, PopupMenuItem}, + progress::Progress, + resizable::{h_resizable, resizable_panel, v_resizable, ResizableState}, + scroll::{ScrollableElement as _, Scrollbar, ScrollbarHandle, ScrollbarShow}, + tab::{Tab, TabBar}, + v_flex, ActiveTheme as _, ElementExt, IconName, Root, Sizable as _, Theme, ThemeMode, + ThemeRegistry, WindowExt as _, +}; +use gpui_component_assets::Assets; +use tokio::runtime::Runtime; +use uuid::Uuid; + +mod config; +mod local_terminal; +mod sftp; +mod ssh_terminal; +mod system; +mod terminal; +mod terminal_element; + +use config::{AuthMethod, ConfigStore, Session}; +use sftp::{format_mtime, RemoteEntry, SftpHandle}; +use system::{format_bytes, DiskSample, SystemSampler, SystemSnapshot}; +use terminal::{encode_key, BackendCommand, BackendEvent, TabKind, TerminalTab}; +use terminal_element::TerminalElement; + +const DEFAULT_COLS: u16 = 100; +const DEFAULT_ROWS: u16 = 30; +const APP_FONT_FAMILY: &str = "Maple Mono NF CN"; +const SIDEBAR_WIDTH: f32 = 306.0; +const TAB_BAR_HEIGHT: f32 = 52.0; +const TERMINAL_PADDING_X: f32 = 32.0; +const TERMINAL_PADDING_Y: f32 = 32.0; +const TERMINAL_KEY_CONTEXT: &str = "AshellTerminal"; +const EMBEDDED_THEME_JSONS: &[&str] = &[ + include_str!("../assets/themes/matrix.json"), + include_str!("../assets/themes/tokyonight.json"), + include_str!("../assets/themes/gruvbox.json"), + include_str!("../assets/themes/solarized.json"), +]; + +gpui::actions!(ashell_terminal, [TerminalTabKey, TerminalBacktabKey]); + +#[derive(Debug, Clone, Copy)] +struct TerminalScrollbarState { + line_height: Pixels, + total_lines: usize, + viewport_lines: usize, + display_offset: usize, +} + +#[derive(Clone, Default)] +struct TerminalScrollbarHandle { + state: Rc>>, + future_display_offset: Rc>>, +} + +impl TerminalScrollbarHandle { + fn update(&self, snapshot: &terminal::RenderSnapshot, line_height: Pixels) { + self.state.replace(Some(TerminalScrollbarState { + line_height, + total_lines: snapshot.history_size + snapshot.rows, + viewport_lines: snapshot.rows, + display_offset: snapshot.display_offset, + })); + } +} + +impl ScrollbarHandle for TerminalScrollbarHandle { + fn offset(&self) -> Point { + let Some(state) = *self.state.borrow() else { + return point(px(0.), px(0.)); + }; + let scroll_offset = state + .total_lines + .saturating_sub(state.viewport_lines) + .saturating_sub(state.display_offset); + point(px(0.), -(scroll_offset as f32 * state.line_height)) + } + + fn set_offset(&self, offset: Point) { + let Some(state) = *self.state.borrow() else { + return; + }; + let offset_delta = (offset.y / state.line_height).round() as i32; + let max_offset = state.total_lines.saturating_sub(state.viewport_lines); + let display_offset = (max_offset as i32 + offset_delta).clamp(0, max_offset as i32); + self.future_display_offset + .set(Some(display_offset as usize)); + } + + fn content_size(&self) -> Size { + let Some(state) = *self.state.borrow() else { + return size(px(0.), px(0.)); + }; + size( + px(0.), + state.total_lines.max(state.viewport_lines) as f32 * state.line_height, + ) + } +} + +struct Ashell { + focus_handle: FocusHandle, + selector_focus_handle: FocusHandle, + host_input: Entity, + session_name_input: Entity, + port_input: Entity, + user_input: Entity, + password_input: Entity, + key_path_input: Entity, + key_inline_input: Entity, + sftp_path_input: Entity, + ssh_auth_method: AuthMethod, + editing_session_id: Option, + follow_system_theme: bool, + light_theme_name: SharedString, + dark_theme_name: SharedString, + terminal_font_size: f32, + tabs: Vec, + sftp_handles: HashMap, + active_tab: Option, + selector_selection: usize, + workspace_panels: Entity, + body_panels: Entity, + terminal_scrollbar: TerminalScrollbarHandle, + remote_files_scroll_handle: UniformListScrollHandle, + connection_progress: Option, + pending_sftp_path_sync: Option, + sftp_context_menu: Option, + show_hidden_files: bool, + system_status: Option, + terminal_bounds: Option>, + terminal_selecting: bool, + terminal_marked_text: Option, + status: SharedString, + config: ConfigStore, + system_sampler: SystemSampler, + system: SystemSnapshot, + last_system_sample: Instant, + remote_sample_in_flight: bool, + runtime: Runtime, + events_rx: mpsc::Receiver, + events_tx: mpsc::Sender, + _subscriptions: Vec, +} + +#[derive(Clone)] +enum SelectorEntry { + Local, + NewSsh, + Saved(String), +} + +#[derive(Clone)] +struct ConnectionProgress { + tab_id: String, + title: SharedString, + lines: Vec, + failed: bool, +} + +#[derive(Clone)] +struct SftpContextMenuState { + remote_path: String, + is_dir: bool, + position: Point, +} + +impl Ashell { + fn new(window: &mut Window, cx: &mut Context) -> Self { + let host_input = cx.new(|cx| InputState::new(window, cx).placeholder("host")); + let session_name_input = + cx.new(|cx| InputState::new(window, cx).placeholder("name (optional)")); + let port_input = cx.new(|cx| InputState::new(window, cx).default_value("22")); + let user_input = cx.new(|cx| InputState::new(window, cx).default_value("root")); + let password_input = cx.new(|cx| { + InputState::new(window, cx) + .placeholder("password") + .masked(true) + }); + let key_path_input = + cx.new(|cx| InputState::new(window, cx).placeholder("~/.ssh/id_ed25519")); + let key_inline_input = cx.new(|cx| { + InputState::new(window, cx) + .multi_line(true) + .rows(5) + .placeholder("-----BEGIN OPENSSH PRIVATE KEY-----") + }); + let sftp_path_input = cx.new(|cx| InputState::new(window, cx).default_value("/")); + + let _subscriptions = vec![ + cx.subscribe_in(&host_input, window, Self::on_input_event), + cx.subscribe_in(&session_name_input, window, Self::on_input_event), + cx.subscribe_in(&port_input, window, Self::on_input_event), + cx.subscribe_in(&user_input, window, Self::on_input_event), + cx.subscribe_in(&password_input, window, Self::on_input_event), + cx.subscribe_in(&key_path_input, window, Self::on_input_event), + cx.subscribe_in(&key_inline_input, window, Self::on_input_event), + cx.subscribe_in(&sftp_path_input, window, Self::on_input_event), + ]; + + let (events_tx, events_rx) = mpsc::channel(); + let workspace_panels = cx.new(|_| ResizableState::default()); + let body_panels = cx.new(|_| ResizableState::default()); + let mut system_sampler = SystemSampler::new(); + let system = system_sampler.sample(); + let default_light_theme_name = ThemeRegistry::global(cx).default_light_theme().name.clone(); + let default_dark_theme_name = ThemeRegistry::global(cx).default_dark_theme().name.clone(); + let config = ConfigStore::load().unwrap_or_else(|err| { + tracing::warn!("failed to load config: {err:#}"); + ConfigStore::in_memory() + }); + let follow_system_theme = + if config.light_theme_name().is_empty() && config.dark_theme_name().is_empty() { + true + } else { + config.follow_system_theme() + }; + let light_theme_name = if config.light_theme_name().is_empty() { + default_light_theme_name + } else { + config.light_theme_name().into() + }; + let dark_theme_name = if config.dark_theme_name().is_empty() { + default_dark_theme_name + } else { + config.dark_theme_name().into() + }; + let mut this = Self { + focus_handle: cx.focus_handle(), + selector_focus_handle: cx.focus_handle(), + host_input, + session_name_input, + port_input, + user_input, + password_input, + key_path_input, + key_inline_input, + sftp_path_input, + ssh_auth_method: AuthMethod::Password, + editing_session_id: None, + follow_system_theme, + light_theme_name, + dark_theme_name, + terminal_font_size: config.terminal_font_size(), + tabs: Vec::new(), + sftp_handles: HashMap::new(), + active_tab: None, + selector_selection: 0, + workspace_panels, + body_panels, + terminal_scrollbar: TerminalScrollbarHandle::default(), + remote_files_scroll_handle: UniformListScrollHandle::new(), + connection_progress: None, + pending_sftp_path_sync: Some("/".into()), + sftp_context_menu: None, + show_hidden_files: false, + system_status: None, + terminal_bounds: None, + terminal_selecting: false, + terminal_marked_text: None, + status: "ready".into(), + config, + system_sampler, + system, + last_system_sample: Instant::now(), + remote_sample_in_flight: false, + runtime: Runtime::new().expect("create tokio runtime"), + events_rx, + events_tx, + _subscriptions, + }; + + this.apply_theme_preferences(window, cx); + // this.open_local(cx); + this.start_event_pump(cx); + this + } + + fn on_input_event( + &mut self, + input: &Entity, + event: &InputEvent, + window: &mut Window, + cx: &mut Context, + ) { + if input == &self.sftp_path_input { + if let InputEvent::PressEnter { .. } = event { + let path = self + .sftp_path_input + .read(cx) + .text() + .to_string() + .trim() + .to_string(); + self.navigate_sftp(if path.is_empty() { "/".into() } else { path }, cx); + window.prevent_default(); + cx.stop_propagation(); + } + } + cx.notify(); + } + + fn start_event_pump(&self, cx: &mut Context) { + cx.spawn(async move |this, cx| loop { + cx.background_executor() + .timer(Duration::from_millis(16)) + .await; + if this + .update(cx, |this, cx| { + this.drain_backend_events(); + this.sample_system_if_due(); + cx.notify(); + }) + .is_err() + { + break; + } + }) + .detach(); + } + + fn drain_backend_events(&mut self) { + while let Ok(event) = self.events_rx.try_recv() { + match event { + BackendEvent::Output { tab_id, bytes } => { + if let Some(tab) = self.tabs.iter_mut().find(|t| t.id == tab_id) { + tab.feed(&bytes); + } + } + BackendEvent::Status { tab_id, text } => { + if let Some(tab) = self.tabs.iter_mut().find(|t| t.id == tab_id) { + tab.status = text.clone(); + } + if let Some(progress) = self.connection_progress.as_mut() { + if progress.tab_id == tab_id { + progress.lines.push(text.clone().into()); + } + } + self.status = text.into(); + } + BackendEvent::Connected { tab_id } => { + if let Some(tab) = self.tabs.iter_mut().find(|t| t.id == tab_id) { + tab.connected = true; + } + self.request_active_system_snapshot(); + if self + .connection_progress + .as_ref() + .is_some_and(|progress| progress.tab_id == tab_id) + { + self.connection_progress = None; + } + } + BackendEvent::SftpEntries { + tab_id, + path, + entries, + } => { + if let Some(tab) = self.tabs.iter_mut().find(|t| t.id == tab_id) { + if let Some(sftp) = tab.sftp.as_mut() { + sftp.current_path = path; + sftp.entries = entries; + if self.active_tab.as_deref() == Some(tab_id.as_str()) { + self.pending_sftp_path_sync = Some(sftp.current_path.clone()); + } + } + } + } + BackendEvent::SftpPreview { tab_id, preview } => { + if let Some(tab) = self.tabs.iter_mut().find(|t| t.id == tab_id) { + if let Some(sftp) = tab.sftp.as_mut() { + sftp.selected_path = Some(preview.path.clone()); + sftp.preview = Some(preview); + } + } + } + BackendEvent::SftpStatus { tab_id, text } => { + if let Some(tab) = self.tabs.iter_mut().find(|t| t.id == tab_id) { + if let Some(sftp) = tab.sftp.as_mut() { + sftp.status = text.clone(); + } + } + self.status = text.into(); + } + BackendEvent::RemoteSystem { tab_id, snapshot } => { + self.remote_sample_in_flight = false; + if self.active_tab.as_deref() == Some(tab_id.as_str()) { + self.system_status = None; + self.system = snapshot; + } + } + BackendEvent::RemoteSystemUnavailable { tab_id, reason } => { + self.remote_sample_in_flight = false; + if self.active_tab.as_deref() == Some(tab_id.as_str()) { + self.system_status = Some(reason.clone().into()); + self.status = reason.into(); + } + } + BackendEvent::Closed { tab_id, reason } => { + self.remote_sample_in_flight = false; + let mut tab_title = None; + let mut session_label = None; + if let Some(tab) = self.tabs.iter_mut().find(|t| t.id == tab_id) { + tab.connected = false; + tab.status = reason.clone(); + tab_title = Some(tab.title.clone()); + session_label = tab.session.as_ref().map(|session| { + format!("{}@{}:{}", session.user, session.host, session.port) + }); + } + if self.active_tab.as_deref() == Some(tab_id.as_str()) { + self.system_status = Some(reason.clone().into()); + } + if let Some(progress) = self.connection_progress.as_mut() { + if progress.tab_id == tab_id { + progress.lines.push(reason.clone().into()); + let _ = session_label; + let _ = tab_title; + progress.title = "Connection Failed".into(); + progress.failed = true; + } + } + self.status = reason.into(); + } + } + } + } + + fn sample_system_if_due(&mut self) { + if self.last_system_sample.elapsed() >= SystemSampler::interval() { + self.request_active_system_snapshot(); + } + } + + fn request_active_system_snapshot(&mut self) { + if let Some((tab_id, session)) = self.active_ssh_session() { + if self.remote_sample_in_flight { + return; + } + self.remote_sample_in_flight = true; + let events = self.events_tx.clone(); + self.runtime.spawn(async move { + match ssh_terminal::sample_remote_system(session).await { + Ok(snapshot) => { + let _ = events.send(BackendEvent::RemoteSystem { tab_id, snapshot }); + } + Err(err) => { + let _ = events.send(BackendEvent::RemoteSystemUnavailable { + tab_id, + reason: format!("remote metrics unavailable: {err:#}"), + }); + } + } + }); + } else { + self.system = self.system_sampler.sample(); + self.system_status = None; + self.remote_sample_in_flight = false; + } + self.last_system_sample = Instant::now(); + } + + fn open_local(&mut self, cx: &mut Context) { + let id = Uuid::new_v4().to_string(); + match local_terminal::spawn_local_terminal( + id.clone(), + DEFAULT_COLS, + DEFAULT_ROWS, + self.events_tx.clone(), + ) { + Ok(backend) => { + let title = if cfg!(windows) { "PowerShell" } else { "Local" }.to_string(); + let mut tab = TerminalTab::new_local(id.clone(), title, backend); + tab.resize(DEFAULT_COLS, DEFAULT_ROWS); + self.tabs.push(tab); + self.active_tab = Some(id); + self.status = "local terminal opened".into(); + } + Err(err) => { + self.status = format!("failed to open local terminal: {err:#}").into(); + } + } + cx.notify(); + } + + fn connect_ssh(&mut self, window: &mut Window, cx: &mut Context) { + let session_name = self.session_name_input.read(cx).value().trim().to_string(); + let host = self.host_input.read(cx).value().trim().to_string(); + let port = self + .port_input + .read(cx) + .value() + .trim() + .parse::() + .unwrap_or(22); + let user = self.user_input.read(cx).value().trim().to_string(); + let password = self.password_input.read(cx).value().to_string(); + let key_path = self.key_path_input.read(cx).value().trim().to_string(); + let key_inline = self.key_inline_input.read(cx).value().to_string(); + + if host.is_empty() || user.is_empty() { + self.status = "host and user are required".into(); + cx.notify(); + return; + } + + let name = if session_name.is_empty() { + host.clone() + } else { + session_name + }; + let existing_id = self.editing_session_id.clone(); + let existing_last_used = existing_id + .as_deref() + .and_then(|id| self.config.get(id)) + .and_then(|session| session.last_used.clone()); + + let mut session = match self.ssh_auth_method { + AuthMethod::Password => Session::password(host, port, user, password), + AuthMethod::Key => { + if key_path.is_empty() && key_inline.trim().is_empty() { + self.status = "private key path or content is required".into(); + cx.notify(); + return; + } + Session::key(host, port, user, key_path, key_inline) + } + }; + session.name = name; + if let Some(id) = existing_id { + session.id = id; + } + session.last_used = existing_last_used; + self.config.upsert(session.clone()); + if let Err(err) = self.config.save() { + tracing::warn!("failed to save config: {err:#}"); + } + + self.open_ssh_session(session, cx); + self.editing_session_id = None; + window.close_dialog(cx); + } + + fn set_input_value( + input: &Entity, + value: impl Into, + window: &mut Window, + cx: &mut Context, + ) { + input.update(cx, |state, cx| state.set_value(value, window, cx)); + } + + fn reset_ssh_form(&mut self, window: &mut Window, cx: &mut Context) { + self.editing_session_id = None; + self.ssh_auth_method = AuthMethod::Password; + Self::set_input_value(&self.session_name_input, "", window, cx); + Self::set_input_value(&self.host_input, "", window, cx); + Self::set_input_value(&self.port_input, "22", window, cx); + Self::set_input_value(&self.user_input, "root", window, cx); + Self::set_input_value(&self.password_input, "", window, cx); + Self::set_input_value(&self.key_path_input, "", window, cx); + Self::set_input_value(&self.key_inline_input, "", window, cx); + } + + fn load_session_into_form( + &mut self, + session: &Session, + window: &mut Window, + cx: &mut Context, + ) { + self.editing_session_id = Some(session.id.clone()); + self.ssh_auth_method = session.auth; + Self::set_input_value(&self.session_name_input, session.name.clone(), window, cx); + Self::set_input_value(&self.host_input, session.host.clone(), window, cx); + Self::set_input_value(&self.port_input, session.port.to_string(), window, cx); + Self::set_input_value(&self.user_input, session.user.clone(), window, cx); + Self::set_input_value(&self.password_input, session.password.clone(), window, cx); + Self::set_input_value( + &self.key_path_input, + session.private_key_path.clone(), + window, + cx, + ); + Self::set_input_value( + &self.key_inline_input, + session.private_key_inline.clone(), + window, + cx, + ); + } + + fn open_new_ssh_dialog(&mut self, window: &mut Window, cx: &mut Context) { + self.reset_ssh_form(window, cx); + self.show_ssh_dialog(window, cx); + } + + fn edit_saved_session( + &mut self, + session_id: String, + window: &mut Window, + cx: &mut Context, + ) { + let Some(session) = self.config.get(&session_id).cloned() else { + self.status = "saved session not found".into(); + cx.notify(); + return; + }; + self.load_session_into_form(&session, window, cx); + self.show_ssh_dialog(window, cx); + } + + fn terminal_cell_width(&self) -> f32 { + (self.terminal_font_size * 0.646).max(6.0) + } + + fn terminal_line_height(&self) -> f32 { + (self.terminal_font_size * 1.385).max(self.terminal_font_size + 2.0) + } + + fn change_terminal_font_size(&mut self, delta: f32, cx: &mut Context) { + self.terminal_font_size = (self.terminal_font_size + delta).clamp(10.0, 24.0); + self.config.set_terminal_font_size(self.terminal_font_size); + if let Err(err) = self.config.save() { + tracing::warn!("failed to save terminal font size: {err:#}"); + } + self.status = format!("terminal font size: {:.0}px", self.terminal_font_size).into(); + cx.notify(); + } + + fn show_ssh_dialog(&mut self, window: &mut Window, cx: &mut Context) { + let view = cx.entity(); + let session_name_input = self.session_name_input.clone(); + let host_input = self.host_input.clone(); + let focus_host_input = host_input.clone(); + let port_input = self.port_input.clone(); + let user_input = self.user_input.clone(); + let password_input = self.password_input.clone(); + let key_path_input = self.key_path_input.clone(); + let key_inline_input = self.key_inline_input.clone(); + + window.open_dialog(cx, move |dialog: Dialog, _window, _cx| { + dialog + .title("SSH Session") + .w(px(520.)) + .overlay_closable(true) + .content({ + let view = view.clone(); + let session_name_input = session_name_input.clone(); + let host_input = host_input.clone(); + let port_input = port_input.clone(); + let user_input = user_input.clone(); + let password_input = password_input.clone(); + let key_path_input = key_path_input.clone(); + let key_inline_input = key_inline_input.clone(); + move |content, window, cx| { + let is_password = view.read(cx).ssh_auth_method == AuthMethod::Password; + let is_editing = view.read(cx).editing_session_id.is_some(); + content.child( + v_flex() + .gap_3() + .child(Input::new(&session_name_input).tab_index(0)) + .child(Input::new(&host_input).tab_index(1)) + .child( + h_flex() + .gap_2() + .child(Input::new(&port_input).w(px(96.)).tab_index(2)) + .child(Input::new(&user_input).flex_1().tab_index(3)), + ) + .child( + h_flex() + .gap_2() + .child( + Button::new("ssh-auth-password") + .label("password") + .when(is_password, |button| button.primary()) + .on_click(window.listener_for( + &view, + |this, _, _, cx| { + this.set_ssh_auth_method( + AuthMethod::Password, + cx, + ) + }, + )), + ) + .child( + Button::new("ssh-auth-key") + .label("key") + .when(!is_password, |button| button.primary()) + .on_click(window.listener_for( + &view, + |this, _, _, cx| { + this.set_ssh_auth_method( + AuthMethod::Key, + cx, + ) + }, + )), + ), + ) + .when(is_password, |this| { + this.child( + Input::new(&password_input).mask_toggle().tab_index(4), + ) + }) + .when(!is_password, |this| { + this.child(Input::new(&key_path_input).tab_index(5)).child( + Input::new(&key_inline_input).h(px(128.)).tab_index(6), + ) + }) + .child( + h_flex() + .justify_end() + .gap_2() + .child( + Button::new("connect-ssh-cancel") + .label("cancel") + .on_click(|_, window, cx| window.close_dialog(cx)), + ) + .child( + Button::new("connect-ssh-confirm") + .primary() + .label(if is_editing { "save" } else { "connect" }) + .on_click(window.listener_for( + &view, + |this, _, window, cx| { + this.connect_ssh(window, cx) + }, + )), + ), + ), + ) + } + }) + }); + window.defer(cx, move |window, cx| { + window.focus(&focus_host_input.read(cx).focus_handle(cx), cx); + }); + } + + fn show_selector_dialog(&mut self, window: &mut Window, cx: &mut Context) { + let view = cx.entity(); + let selector_focus_handle = self.selector_focus_handle.clone(); + let deferred_selector_focus_handle = selector_focus_handle.clone(); + let sessions = self.config.sessions().to_vec(); + let active_session_id = self.active_session_id().map(ToOwned::to_owned); + self.selector_selection = self.default_selector_index(); + window.open_dialog(cx, move |dialog: Dialog, _window, _| { + dialog + .title("Open Session") + .w(px(520.)) + .on_ok({ + let view = view.clone(); + move |_, window, cx| { + view.update(cx, |this, cx| { + this.activate_selector_selection(window, cx); + }); + true + } + }) + .content({ + let view = view.clone(); + let sessions = sessions.clone(); + let _active_session_id = active_session_id.clone(); + let selector_focus_handle = selector_focus_handle.clone(); + move |content, window, _cx| { + let selected_index = view.read(_cx).selector_selection; + content.child( + v_flex() + .track_focus(&selector_focus_handle) + .on_key_down(window.listener_for( + &view, + |this, event, window, cx| { + this.on_selector_key_down(event, window, cx) + }, + )) + .gap_2() + .child( + div() + .w_full() + .p_2() + .rounded_md() + .border_1() + .border_color(if selected_index == 0 { + _cx.theme().primary + } else { + _cx.theme().border + }) + .bg(if selected_index == 0 { + _cx.theme().tab_active + } else { + _cx.theme().muted + }) + .cursor_pointer() + .hover(|this| this.bg(_cx.theme().secondary)) + .on_mouse_down( + MouseButton::Left, + window.listener_for(&view, |this, _, window, cx| { + this.open_local(cx); + window.close_dialog(cx); + }), + ) + .child( + v_flex() + .gap_1() + .child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::SEMIBOLD) + .child("Local Terminal"), + ) + .child( + div() + .text_size(px(11.)) + .text_color(_cx.theme().muted_foreground) + .child("Open a new local shell tab"), + ), + ), + ) + .child( + div() + .w_full() + .p_2() + .rounded_md() + .border_1() + .border_color(if selected_index == 1 { + _cx.theme().primary + } else { + _cx.theme().border + }) + .bg(if selected_index == 1 { + _cx.theme().tab_active + } else { + _cx.theme().muted + }) + .cursor_pointer() + .hover(|this| this.bg(_cx.theme().secondary)) + .on_mouse_down( + MouseButton::Left, + window.listener_for(&view, |this, _, window, cx| { + window.close_dialog(cx); + this.open_new_ssh_dialog(window, cx); + }), + ) + .child( + v_flex() + .gap_1() + .child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::SEMIBOLD) + .child("New SSH Connection"), + ) + .child( + div() + .text_size(px(11.)) + .text_color(_cx.theme().muted_foreground) + .child("Create or edit an SSH session"), + ), + ), + ) + .child( + v_flex() + .max_h(px(320.)) + .overflow_scrollbar() + .gap_2() + .children(sessions.clone().into_iter().enumerate().map( + |(ix, session)| { + let connect_id = session.id.clone(); + let is_selected = selected_index == ix + 2; + let name = session.name.clone(); + let detail = format!( + "{}@{}:{}", + session.user, session.host, session.port + ); + div() + .id(("selector-open", ix)) + .w_full() + .p_2() + .rounded_md() + .border_1() + .border_color(if is_selected { + _cx.theme().primary + } else { + _cx.theme().border + }) + .bg(if is_selected { + _cx.theme().tab_active + } else { + _cx.theme().muted + }) + .cursor_pointer() + .hover(|this| this.bg(_cx.theme().secondary)) + .on_mouse_down( + MouseButton::Left, + window.listener_for( + &view, + move |this, _, window, cx| { + this.connect_saved_session( + connect_id.clone(), + cx, + ); + window.close_dialog(cx); + }, + ), + ) + .child( + v_flex() + .gap_1() + .child( + div() + .text_size(px(12.)) + .font_weight( + FontWeight::SEMIBOLD, + ) + .child(name), + ) + .child( + div() + .text_size(px(11.)) + .text_color( + _cx.theme() + .muted_foreground, + ) + .child(detail), + ), + ) + }, + )), + ), + ) + } + }) + }); + window.defer(cx, move |window, cx| { + window.focus(&deferred_selector_focus_handle, cx); + }); + } + + fn show_settings_dialog(&mut self, window: &mut Window, cx: &mut Context) { + let view = cx.entity(); + window.open_dialog(cx, move |dialog: Dialog, _window, _| { + dialog + .title("Settings") + .w(px(480.)) + .content({ + let view = view.clone(); + move |content, window, cx| { + content.child( + v_flex() + .gap_3() + .child( + h_flex() + .items_center() + .gap_3() + .child(div().w(px(180.)).child("Terminal Font Size")) + .child( + Button::new("font-size-down") + .label("-") + .on_click(window.listener_for(&view, |this, _, _, cx| { + this.change_terminal_font_size(-1.0, cx) + })), + ) + .child( + div() + .min_w(px(64.)) + .text_center() + .child(format!("{:.0}px", view.read(cx).terminal_font_size)), + ) + .child( + Button::new("font-size-up") + .label("+") + .on_click(window.listener_for(&view, |this, _, _, cx| { + this.change_terminal_font_size(1.0, cx) + })), + ), + ) + .child( + div() + .text_size(px(12.)) + .text_color(cx.theme().muted_foreground) + .child("Theme mode and light/dark theme families are managed from the top-bar theme button."), + ), + ) + } + }) + }); + } + + fn set_ssh_auth_method(&mut self, method: AuthMethod, cx: &mut Context) { + self.ssh_auth_method = method; + cx.notify(); + } + + fn connect_saved_session(&mut self, session_id: String, cx: &mut Context) { + let Some(session) = self.config.get(&session_id).cloned() else { + self.status = "saved session not found".into(); + cx.notify(); + return; + }; + self.open_ssh_session(session, cx); + } + + fn selector_entries(&self) -> Vec { + let mut entries = vec![SelectorEntry::Local, SelectorEntry::NewSsh]; + entries.extend( + self.config + .sessions() + .iter() + .map(|session| SelectorEntry::Saved(session.id.clone())), + ); + entries + } + + fn default_selector_index(&self) -> usize { + if self.config.sessions().is_empty() { + 0 + } else { + 2 + } + } + + fn move_selector_selection(&mut self, delta: i32, cx: &mut Context) { + let entries = self.selector_entries(); + if entries.is_empty() { + return; + } + let current = self.selector_selection.min(entries.len().saturating_sub(1)) as i32; + let next = (current + delta).clamp(0, entries.len() as i32 - 1) as usize; + if next != self.selector_selection { + self.selector_selection = next; + cx.notify(); + } + } + + fn activate_selector_selection(&mut self, window: &mut Window, cx: &mut Context) { + let entries = self.selector_entries(); + let Some(entry) = entries.get(self.selector_selection).cloned() else { + return; + }; + + match entry { + SelectorEntry::Local => { + self.open_local(cx); + window.close_dialog(cx); + } + SelectorEntry::NewSsh => { + window.close_dialog(cx); + self.open_new_ssh_dialog(window, cx); + } + SelectorEntry::Saved(session_id) => { + self.connect_saved_session(session_id, cx); + window.close_dialog(cx); + } + } + } + + fn on_selector_key_down( + &mut self, + event: &KeyDownEvent, + window: &mut Window, + cx: &mut Context, + ) { + let key = event.keystroke.key.to_ascii_lowercase(); + match key.as_str() { + "up" | "arrowup" => { + self.move_selector_selection(-1, cx); + window.prevent_default(); + cx.stop_propagation(); + } + "down" | "arrowdown" => { + self.move_selector_selection(1, cx); + window.prevent_default(); + cx.stop_propagation(); + } + "enter" | "return" => { + self.activate_selector_selection(window, cx); + window.prevent_default(); + cx.stop_propagation(); + } + _ => {} + } + } + + fn open_ssh_session(&mut self, session: Session, cx: &mut Context) { + let id = Uuid::new_v4().to_string(); + let backend = ssh_terminal::spawn_ssh_terminal( + self.runtime.handle(), + id.clone(), + session.clone(), + DEFAULT_COLS, + DEFAULT_ROWS, + self.events_tx.clone(), + ); + self.tabs + .push(TerminalTab::new_ssh(id.clone(), &session, backend)); + let sftp_handle = sftp::spawn_sftp( + self.runtime.handle(), + id.clone(), + session, + self.events_tx.clone(), + ); + self.sftp_handles.insert(id.clone(), sftp_handle); + self.active_tab = Some(id.clone()); + self.pending_sftp_path_sync = Some("/".into()); + self.connection_progress = Some(ConnectionProgress { + tab_id: id, + title: "Connecting".into(), + lines: vec!["starting connection...".into()], + failed: false, + }); + self.status = "ssh tab opened".into(); + cx.notify(); + } + + fn remove_saved_session(&mut self, session_id: String, cx: &mut Context) { + self.config.remove(&session_id); + if let Err(err) = self.config.save() { + tracing::warn!("failed to save config: {err:#}"); + } + self.status = "session removed".into(); + cx.notify(); + } + + fn retry_connection_progress(&mut self, cx: &mut Context) { + let Some(progress) = self.connection_progress.clone() else { + return; + }; + let Some(ix) = self.tabs.iter().position(|tab| tab.id == progress.tab_id) else { + self.connection_progress = None; + cx.notify(); + return; + }; + let Some(session) = self.tabs[ix].session.clone() else { + self.connection_progress = None; + cx.notify(); + return; + }; + + self.tabs[ix].backend.send(BackendCommand::Close); + if let Some(handle) = self.sftp_handles.remove(&progress.tab_id) { + handle.close(); + } + self.tabs.remove(ix); + if self.active_tab.as_deref() == Some(progress.tab_id.as_str()) { + self.active_tab = self + .tabs + .get(ix) + .or_else(|| self.tabs.get(ix.saturating_sub(1))) + .map(|tab| tab.id.clone()); + } + self.connection_progress = None; + self.open_ssh_session(session, cx); + } + + fn cancel_connection_progress(&mut self, cx: &mut Context) { + let Some(progress) = self.connection_progress.clone() else { + return; + }; + self.connection_progress = None; + self.close_tab(progress.tab_id, cx); + } + + fn activate_tab(&mut self, id: String, window: &mut Window, cx: &mut Context) { + if let Some(path) = self + .tabs + .iter() + .find(|tab| tab.id == id) + .and_then(|tab| tab.sftp.as_ref()) + .map(|sftp| sftp.current_path.clone()) + { + self.pending_sftp_path_sync = Some(path); + } + self.active_tab = Some(id); + self.remote_sample_in_flight = false; + self.request_active_system_snapshot(); + self.focus_handle.focus(window, cx); + cx.notify(); + } + + fn close_tab(&mut self, id: String, cx: &mut Context) { + if let Some(ix) = self.tabs.iter().position(|tab| tab.id == id) { + let was_active = self.active_tab.as_deref() == Some(id.as_str()); + self.tabs[ix].backend.send(BackendCommand::Close); + if let Some(handle) = self.sftp_handles.remove(&id) { + handle.close(); + } + self.tabs.remove(ix); + if was_active + || self + .active_tab + .as_ref() + .is_some_and(|active_id| !self.tabs.iter().any(|tab| &tab.id == active_id)) + { + self.active_tab = self + .tabs + .get(ix) + .or_else(|| self.tabs.get(ix.saturating_sub(1))) + .map(|tab| tab.id.clone()); + self.remote_sample_in_flight = false; + self.request_active_system_snapshot(); + } + cx.notify(); + } + } + + fn active_sftp(&self) -> Option<&terminal::SftpUiState> { + self.active_tab + .as_ref() + .and_then(|id| self.tabs.iter().find(|tab| &tab.id == id)) + .and_then(|tab| tab.sftp.as_ref()) + } + + fn active_sftp_mut(&mut self) -> Option<&mut terminal::SftpUiState> { + let active_id = self.active_tab.clone()?; + self.tabs + .iter_mut() + .find(|tab| tab.id == active_id) + .and_then(|tab| tab.sftp.as_mut()) + } + + fn active_sftp_handle(&self) -> Option<&SftpHandle> { + self.active_tab + .as_ref() + .and_then(|id| self.sftp_handles.get(id)) + } + + fn navigate_sftp(&mut self, path: String, cx: &mut Context) { + if let Some(handle) = self.active_sftp_handle() { + handle.list_dir(path.clone()); + if let Some(sftp) = self.active_sftp_mut() { + sftp.current_path = path; + self.pending_sftp_path_sync = Some(sftp.current_path.clone()); + } + cx.notify(); + } + } + + fn select_sftp_entry(&mut self, entry: RemoteEntry, cx: &mut Context) { + if entry.is_dir { + self.navigate_sftp(entry.full_path, cx); + return; + } + self.mark_sftp_entry_selected(&entry.full_path, cx); + } + + fn mark_sftp_entry_selected(&mut self, path: &str, cx: &mut Context) { + if let Some(sftp) = self.active_sftp_mut() { + sftp.selected_path = Some(path.to_string()); + } + cx.notify(); + } + + fn sftp_parent_path(path: &str) -> String { + if path == "/" { + return "/".to_string(); + } + path.trim_end_matches('/') + .rsplit_once('/') + .map(|(parent, _)| { + if parent.is_empty() { + "/".to_string() + } else { + parent.to_string() + } + }) + .unwrap_or_else(|| "/".to_string()) + } + + fn refresh_sftp(&mut self, cx: &mut Context) { + if let Some(path) = self.active_sftp().map(|sftp| sftp.current_path.clone()) { + self.navigate_sftp(path, cx); + } + } + + fn sync_sftp_path_input(&mut self, window: &mut Window, cx: &mut Context) { + let Some(path) = self.pending_sftp_path_sync.take() else { + return; + }; + self.sftp_path_input.update(cx, |state, cx| { + state.set_value(path, window, cx); + }); + } + + fn open_sftp_context_menu( + &mut self, + remote_path: String, + is_dir: bool, + position: Point, + cx: &mut Context, + ) { + self.sftp_context_menu = Some(SftpContextMenuState { + remote_path, + is_dir, + position, + }); + cx.notify(); + } + + fn dismiss_sftp_context_menu(&mut self, cx: &mut Context) { + if self.sftp_context_menu.take().is_some() { + cx.notify(); + } + } + + fn trigger_sftp_context_download(&mut self, window: &mut Window, cx: &mut Context) { + let Some(menu) = self.sftp_context_menu.take() else { + return; + }; + self.download_sftp_entry(menu.remote_path, window, cx); + cx.notify(); + } + + fn download_sftp_entry( + &mut self, + remote_path: String, + window: &mut Window, + cx: &mut Context, + ) { + let Some(handle) = self.active_sftp_handle().cloned() else { + return; + }; + let path_prompt = cx.prompt_for_paths(PathPromptOptions { + files: false, + directories: true, + multiple: false, + prompt: Some("Select Download Folder".into()), + }); + cx.spawn_in(window, async move |this, cx| { + match path_prompt.await { + Ok(Ok(Some(mut paths))) => { + if let Some(folder) = paths.pop() { + handle.download(remote_path, folder.to_string_lossy().to_string()); + } + } + Ok(Err(err)) => { + this.update(cx, |this, cx| { + this.status = format!("download picker failed: {err}").into(); + cx.notify(); + })?; + } + _ => {} + } + Ok::<(), anyhow::Error>(()) + }) + .detach(); + } + + fn upload_sftp_files(&mut self, window: &mut Window, cx: &mut Context) { + let Some(handle) = self.active_sftp_handle().cloned() else { + return; + }; + let remote_dir = self + .active_sftp() + .map(|sftp| sftp.current_path.clone()) + .unwrap_or_else(|| "/".into()); + let path_prompt = cx.prompt_for_paths(PathPromptOptions { + files: true, + directories: false, + multiple: false, + prompt: Some("Select File to Upload".into()), + }); + cx.spawn_in(window, async move |this, cx| { + match path_prompt.await { + Ok(Ok(Some(mut paths))) => { + if let Some(file) = paths.pop() { + handle.upload_paths(vec![file.to_string_lossy().to_string()], remote_dir); + } + } + Ok(Err(err)) => { + this.update(cx, |this, cx| { + this.status = format!("upload picker failed: {err}").into(); + cx.notify(); + })?; + } + _ => {} + } + Ok::<(), anyhow::Error>(()) + }) + .detach(); + } + + fn upload_sftp_folder(&mut self, window: &mut Window, cx: &mut Context) { + let Some(handle) = self.active_sftp_handle().cloned() else { + return; + }; + let remote_dir = self + .active_sftp() + .map(|sftp| sftp.current_path.clone()) + .unwrap_or_else(|| "/".into()); + let path_prompt = cx.prompt_for_paths(PathPromptOptions { + files: false, + directories: true, + multiple: false, + prompt: Some("Select Folder to Upload".into()), + }); + cx.spawn_in(window, async move |this, cx| { + match path_prompt.await { + Ok(Ok(Some(mut paths))) => { + if let Some(folder) = paths.pop() { + handle.upload_paths(vec![folder.to_string_lossy().to_string()], remote_dir); + } + } + Ok(Err(err)) => { + this.update(cx, |this, cx| { + this.status = format!("upload picker failed: {err}").into(); + cx.notify(); + })?; + } + _ => {} + } + Ok::<(), anyhow::Error>(()) + }) + .detach(); + } + + fn switch_theme_mode(&mut self, mode: ThemeMode, window: &mut Window, cx: &mut Context) { + self.follow_system_theme = false; + self.apply_theme_preferences(window, cx); + Theme::change(mode, Some(window), cx); + self.status = format!("theme mode: {}", cx.theme().mode.name()).into(); + self.persist_theme_preferences(); + cx.notify(); + } + + fn apply_theme(&mut self, name: SharedString, window: &mut Window, cx: &mut Context) { + let Some(theme_config) = ThemeRegistry::global(cx).themes().get(&name).cloned() else { + self.status = format!("theme not found: {name}").into(); + cx.notify(); + return; + }; + + if theme_config.mode.is_dark() { + self.dark_theme_name = name.clone(); + } else { + self.light_theme_name = name.clone(); + } + self.apply_theme_preferences(window, cx); + self.status = format!("theme: {name}").into(); + self.persist_theme_preferences(); + window.refresh(); + cx.notify(); + } + + fn set_follow_system_theme( + &mut self, + follow: bool, + window: &mut Window, + cx: &mut Context, + ) { + self.follow_system_theme = follow; + if follow { + self.status = "theme mode: system".into(); + } else { + self.status = format!("theme mode: {}", cx.theme().mode.name()).into(); + } + self.apply_theme_preferences(window, cx); + self.persist_theme_preferences(); + cx.notify(); + } + + fn apply_theme_preferences(&mut self, window: &mut Window, cx: &mut Context) { + let light_theme = ThemeRegistry::global(cx) + .themes() + .get(&self.light_theme_name) + .cloned() + .unwrap_or_else(|| ThemeRegistry::global(cx).default_light_theme().clone()); + let dark_theme = ThemeRegistry::global(cx) + .themes() + .get(&self.dark_theme_name) + .cloned() + .unwrap_or_else(|| ThemeRegistry::global(cx).default_dark_theme().clone()); + let theme = Theme::global_mut(cx); + theme.light_theme = light_theme; + theme.dark_theme = dark_theme; + set_theme_font_names(theme); + + if self.follow_system_theme { + Theme::sync_system_appearance(Some(window), cx); + } else { + Theme::change(cx.theme().mode, Some(window), cx); + } + } + + fn persist_theme_preferences(&mut self) { + self.config.set_theme_preferences( + self.follow_system_theme, + self.light_theme_name.to_string(), + self.dark_theme_name.to_string(), + ); + if let Err(err) = self.config.save() { + tracing::warn!("failed to save theme preferences: {err:#}"); + } + } + + fn on_terminal_key_down( + &mut self, + event: &KeyDownEvent, + window: &mut Window, + cx: &mut Context, + ) { + if event.keystroke.modifiers.secondary() && event.keystroke.key == "," { + self.show_settings_dialog(window, cx); + window.prevent_default(); + cx.stop_propagation(); + return; + } + if event.keystroke.modifiers.shift + && event.keystroke.modifiers.secondary() + && event.keystroke.key == "o" + { + self.show_selector_dialog(window, cx); + window.prevent_default(); + cx.stop_propagation(); + return; + } + if event.keystroke.modifiers.secondary() && event.keystroke.key.eq_ignore_ascii_case("c") { + if let Some(text) = self.active_terminal_selection_text() { + cx.write_to_clipboard(ClipboardItem::new_string(text)); + window.prevent_default(); + cx.stop_propagation(); + return; + } + } + if event.keystroke.modifiers.secondary() && event.keystroke.key.eq_ignore_ascii_case("v") { + if let Some(clipboard) = cx.read_from_clipboard() { + if let Some(text) = clipboard.text() { + self.paste_into_terminal(&text, window, cx); + return; + } + } + } + + if event.prefer_character_input { + if let Some(text) = event.keystroke.key_char.as_deref() { + if !text.is_empty() + && !event.keystroke.modifiers.control + && !event.keystroke.modifiers.function + && !event.keystroke.modifiers.platform + { + self.send_terminal_input(text.as_bytes().to_vec(), window, cx); + } + } + return; + } + + let Some(active_id) = self.active_tab.clone() else { + return; + }; + let Some(tab) = self.tabs.iter_mut().find(|t| t.id == active_id) else { + return; + }; + + if tab.render_snapshot().display_offset > 0 { + tab.scroll_to_bottom(); + } + tab.clear_selection(); + + if let Some(bytes) = encode_key(&event.keystroke, tab.app_cursor_mode(), false) { + tab.backend.send(BackendCommand::Input(bytes)); + window.prevent_default(); + cx.stop_propagation(); + cx.notify(); + } + } + + fn on_terminal_tab_action( + &mut self, + _: &TerminalTabKey, + window: &mut Window, + cx: &mut Context, + ) { + self.send_terminal_input(vec![b'\t'], window, cx); + } + + fn on_terminal_backtab_action( + &mut self, + _: &TerminalBacktabKey, + window: &mut Window, + cx: &mut Context, + ) { + self.send_terminal_input(b"\x1b[Z".to_vec(), window, cx); + } + + fn send_terminal_input(&mut self, bytes: Vec, window: &mut Window, cx: &mut Context) { + let Some(active_id) = self.active_tab.clone() else { + return; + }; + let Some(tab) = self.tabs.iter_mut().find(|t| t.id == active_id) else { + return; + }; + + if tab.render_snapshot().display_offset > 0 { + tab.scroll_to_bottom(); + } + + tab.clear_selection(); + tab.backend.send(BackendCommand::Input(bytes)); + window.prevent_default(); + cx.stop_propagation(); + cx.notify(); + } + + fn active_terminal_selection_text(&self) -> Option { + let active_id = self.active_tab.as_ref()?; + self.tabs + .iter() + .find(|tab| &tab.id == active_id) + .and_then(|tab| tab.selection_text()) + } + + fn paste_into_terminal(&mut self, text: &str, window: &mut Window, cx: &mut Context) { + let Some(active_id) = self.active_tab.clone() else { + return; + }; + let Some(tab) = self.tabs.iter_mut().find(|tab| tab.id == active_id) else { + return; + }; + + if tab.render_snapshot().display_offset > 0 { + tab.scroll_to_bottom(); + } + tab.clear_selection(); + tab.paste_text(text); + window.prevent_default(); + cx.stop_propagation(); + cx.notify(); + } + + pub(crate) fn terminal_accepts_text_input(&self) -> bool { + self.active_tab.is_some() + } + + pub(crate) fn terminal_marked_text_range(&self) -> Option> { + self.terminal_marked_text + .as_ref() + .map(|text| 0..text.encode_utf16().count()) + } + + pub(crate) fn set_terminal_marked_text( + &mut self, + text: String, + window: &mut Window, + cx: &mut Context, + ) { + self.terminal_marked_text = if text.is_empty() { None } else { Some(text) }; + window.invalidate_character_coordinates(); + cx.notify(); + } + + pub(crate) fn clear_terminal_marked_text( + &mut self, + window: &mut Window, + cx: &mut Context, + ) { + if self.terminal_marked_text.take().is_some() { + window.invalidate_character_coordinates(); + cx.notify(); + } + } + + pub(crate) fn commit_terminal_ime_text( + &mut self, + text: &str, + window: &mut Window, + cx: &mut Context, + ) { + let Some(active_id) = self.active_tab.clone() else { + return; + }; + let Some(tab) = self.tabs.iter_mut().find(|tab| tab.id == active_id) else { + return; + }; + + if tab.render_snapshot().display_offset > 0 { + tab.scroll_to_bottom(); + } + tab.clear_selection(); + self.terminal_marked_text = None; + tab.backend + .send(BackendCommand::Input(text.as_bytes().to_vec())); + window.invalidate_character_coordinates(); + cx.notify(); + } + + pub(crate) fn terminal_ime_bounds_for_range( + &self, + range_utf16: Range, + element_bounds: Bounds, + ) -> Option> { + let snapshot = self.active_snapshot()?; + let cursor = snapshot.cursor?; + let x = element_bounds.origin.x + + px(self.terminal_cell_width()) * cursor.col as f32 + + px(self.terminal_cell_width()) * range_utf16.start as f32; + let y = element_bounds.origin.y + px(self.terminal_line_height()) * cursor.row as f32; + Some(Bounds::new( + point(x, y), + size( + px(self.terminal_cell_width()), + px(self.terminal_line_height()), + ), + )) + } + + fn focus_terminal( + &mut self, + event: &MouseDownEvent, + window: &mut Window, + cx: &mut Context, + ) { + self.focus_handle.focus(window, cx); + if event.button == MouseButton::Left { + self.begin_terminal_selection(event, cx); + } + cx.notify(); + } + + fn begin_terminal_selection(&mut self, event: &MouseDownEvent, cx: &mut Context) { + let click_count = event.click_count.max(1); + let selection_type = match click_count { + 1 => SelectionType::Simple, + 2 => SelectionType::Semantic, + 3 => SelectionType::Lines, + _ => SelectionType::Simple, + }; + let Some((row, col, side)) = self.terminal_grid_point_and_side(event.position) else { + return; + }; + let Some(active_id) = self.active_tab.clone() else { + return; + }; + if let Some(tab) = self.tabs.iter_mut().find(|tab| tab.id == active_id) { + tab.begin_selection(row, col, side, selection_type); + self.terminal_selecting = true; + cx.notify(); + } + } + + fn on_terminal_mouse_move( + &mut self, + event: &MouseMoveEvent, + _window: &mut Window, + cx: &mut Context, + ) { + if !self.terminal_selecting || event.pressed_button != Some(MouseButton::Left) { + return; + } + let Some((row, col, side)) = self.terminal_grid_point_and_side(event.position) else { + return; + }; + let Some(active_id) = self.active_tab.clone() else { + return; + }; + if let Some(tab) = self.tabs.iter_mut().find(|tab| tab.id == active_id) { + tab.update_selection(row, col, side); + cx.notify(); + } + } + + fn on_terminal_mouse_up( + &mut self, + _event: &MouseUpEvent, + _window: &mut Window, + cx: &mut Context, + ) { + self.terminal_selecting = false; + cx.notify(); + } + + fn terminal_grid_point_and_side( + &self, + position: Point, + ) -> Option<(usize, usize, Side)> { + let bounds = self.terminal_bounds?; + if !bounds.contains(&position) { + return None; + } + let local_x = (position.x - bounds.origin.x).max(px(0.)); + let local_y = (position.y - bounds.origin.y).max(px(0.)); + let cell_width = px(self.terminal_cell_width()); + let line_height = px(self.terminal_line_height()); + let snapshot = self.active_snapshot()?; + let max_col = snapshot.cols.saturating_sub(1); + let max_row = snapshot.rows.saturating_sub(1); + let col = ((local_x / cell_width).floor() as usize).min(max_col); + let row = ((local_y / line_height).floor() as usize).min(max_row); + let cell_offset_x = px(local_x.as_f32() % cell_width.as_f32()); + let side = if cell_offset_x >= (cell_width / 2.) { + Side::Right + } else { + Side::Left + }; + Some((row, col, side)) + } + + fn on_terminal_scroll( + &mut self, + event: &ScrollWheelEvent, + window: &mut Window, + cx: &mut Context, + ) { + let delta_lines = match event.delta { + ScrollDelta::Lines(point) => point.y.round() as i32, + ScrollDelta::Pixels(point) => { + (point.y.as_f32() / self.terminal_line_height()).round() as i32 + } + }; + if delta_lines == 0 { + return; + } + let Some(active_id) = self.active_tab.clone() else { + return; + }; + if let Some(tab) = self.tabs.iter_mut().find(|tab| tab.id == active_id) { + tab.scroll_history(delta_lines); + window.prevent_default(); + cx.stop_propagation(); + cx.notify(); + } + } + + fn active_snapshot(&self) -> Option { + self.active_tab + .as_ref() + .and_then(|id| self.tabs.iter().find(|t| &t.id == id)) + .map(TerminalTab::render_snapshot) + } + + fn render_home_page(&self, cx: &mut Context) -> impl IntoElement { + v_flex() + .w_full() + .h_full() + .items_center() + .justify_center() + .gap_4() + .child( + div() + .text_size(px(28.)) + .font_weight(FontWeight::BOLD) + .child("ashell"), + ) + .child( + div() + .text_size(px(13.)) + .text_color(cx.theme().muted_foreground) + .child("Open a local terminal or pick an SSH session."), + ) + .child( + h_flex() + .gap_3() + .child( + Button::new("home-open-local") + .primary() + .label("Local Terminal") + .on_click(cx.listener(|this, _, _, cx| this.open_local(cx))), + ) + .child( + Button::new("home-open-session") + .ghost() + .label("Open Session") + .on_click(cx.listener(|this, _, window, cx| { + this.show_selector_dialog(window, cx) + })), + ), + ) + } + + fn active_status(&self) -> String { + self.active_tab + .as_ref() + .and_then(|id| self.tabs.iter().find(|t| &t.id == id)) + .map(|t| t.status.clone()) + .unwrap_or_else(|| self.status.to_string()) + } + + fn active_connection_ok(&self) -> bool { + let status = self.active_status().to_ascii_lowercase(); + let has_error = ["failed", "error", "closed", "unavailable", "required"] + .iter() + .any(|needle| status.contains(needle)); + !has_error && self.active_tab.is_some() + } + + fn active_connection_color(&self, cx: &mut Context) -> Hsla { + if self.active_connection_ok() { + cx.theme().success + } else { + cx.theme().danger + } + } + + fn active_kind(&self) -> Option { + self.active_tab + .as_ref() + .and_then(|id| self.tabs.iter().find(|t| &t.id == id)) + .map(|tab| tab.kind) + } + + fn active_title(&self) -> String { + self.active_tab + .as_ref() + .and_then(|id| self.tabs.iter().find(|t| &t.id == id)) + .map(|t| t.title.clone()) + .unwrap_or_else(|| "No session".into()) + } + + fn active_ssh_session(&self) -> Option<(String, Session)> { + let active_id = self.active_tab.as_ref()?; + let tab = self.tabs.iter().find(|tab| &tab.id == active_id)?; + if !tab.connected { + return None; + } + Some((tab.id.clone(), tab.session.clone()?)) + } + + fn active_session_id(&self) -> Option<&str> { + self.active_tab + .as_ref() + .and_then(|id| self.tabs.iter().find(|tab| &tab.id == id)) + .and_then(|tab| tab.session.as_ref()) + .map(|session| session.id.as_str()) + } + + fn session_detail(&self, session: &Session) -> String { + format!("{}@{}:{}", session.user, session.host, session.port) + } + + fn system_target_label(&self) -> String { + match self.active_kind() { + Some(TabKind::Ssh) => self.active_title(), + Some(TabKind::Local) => "local host".into(), + None => "idle".into(), + } + } + + fn sync_terminal_size(&mut self, window: &Window, cx: &App) { + let viewport = window.viewport_size(); + let sidebar_width = self + .workspace_panels + .read(cx) + .sizes() + .first() + .map(|size| size.as_f32()) + .unwrap_or(SIDEBAR_WIDTH); + let terminal_height = self + .body_panels + .read(cx) + .sizes() + .first() + .map(|size| size.as_f32()) + .unwrap_or(viewport.height.as_f32() - TAB_BAR_HEIGHT - 248.0); + let width = (viewport.width.as_f32() - sidebar_width - TERMINAL_PADDING_X - 8.0) + .max(self.terminal_cell_width()); + let height = (terminal_height - TERMINAL_PADDING_Y).max(self.terminal_line_height()); + let cols = (width / self.terminal_cell_width()).floor().max(1.0) as u16; + let rows = (height / self.terminal_line_height()).floor().max(1.0) as u16; + + for tab in &mut self.tabs { + tab.resize(cols, rows); + } + } + + fn system_card( + &self, + label: &'static str, + percent: f32, + detail: String, + fill: Hsla, + cx: &mut Context, + ) -> impl IntoElement { + let percent = percent.clamp(0.0, 1.0); + v_flex() + .gap_1() + .p_2() + .rounded_md() + .border_1() + .border_color(cx.theme().border) + .bg(cx.theme().muted) + .child( + h_flex() + .items_center() + .gap_2() + .child( + div() + .text_size(px(11.)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(fill) + .child(label), + ) + .child(div().flex_1()) + .child( + div() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .flex_none() + .child(format!("{:.0}%", percent * 100.0)), + ), + ) + .child( + Progress::new(format!("system-progress-{label}")) + .with_size(px(6.)) + .value(percent * 100.0) + .color(fill) + .w_full(), + ) + .child( + div() + .w_full() + .min_w(px(0.)) + .overflow_hidden() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child(detail), + ) + } + + fn network_card(&self, cx: &mut Context) -> impl IntoElement { + v_flex() + .gap_2() + .p_2() + .rounded_md() + .border_1() + .border_color(cx.theme().border) + .bg(cx.theme().muted) + .child( + h_flex() + .items_center() + .gap_2() + .child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(cx.theme().chart_4) + .child("NET"), + ) + .child(div().flex_1()) + .child( + div() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child("live"), + ), + ) + .child( + h_flex() + .gap_2() + .child( + h_flex() + .flex_1() + .min_w(px(0.)) + .gap_1() + .child( + div() + .flex_none() + .text_size(px(12.)) + .text_color(cx.theme().chart_4) + .child("↓"), + ) + .child(div().text_size(px(12.)).child(self.system.net_rx.clone())), + ) + .child( + h_flex() + .flex_1() + .min_w(px(0.)) + .gap_1() + .child( + div() + .flex_none() + .text_size(px(12.)) + .text_color(cx.theme().chart_5) + .child("↑"), + ) + .child(div().text_size(px(12.)).child(self.system.net_tx.clone())), + ), + ) + } + + fn disk_row(&self, disk: DiskSample, cx: &mut Context) -> impl IntoElement + use<> { + let mount = disk.mount.clone(); + let used = disk.total_bytes.saturating_sub(disk.available_bytes); + let percent = if disk.total_bytes == 0 { + 0.0 + } else { + used as f32 / disk.total_bytes as f32 + } + .clamp(0.0, 1.0); + + v_flex() + .gap_1() + .child( + h_flex() + .items_center() + .gap_2() + .child( + div() + .flex_1() + .min_w(px(0.)) + .overflow_hidden() + .text_size(px(11.)) + .text_color(cx.theme().foreground) + .child(mount.clone()), + ) + .child( + div() + .flex_none() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child(format!("{:.0}%", percent * 100.0)), + ), + ) + .child( + Progress::new(format!("disk-progress-{mount}")) + .with_size(px(5.)) + .value(percent * 100.0) + .color(cx.theme().chart_5) + .w_full(), + ) + .child( + div() + .min_w(px(0.)) + .overflow_hidden() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child(format!( + "{}/{}", + format_bytes(used), + format_bytes(disk.total_bytes) + )), + ) + } + + fn theme_dropdown(&self, cx: &mut Context) -> impl IntoElement { + let view = cx.entity(); + let themes = ThemeRegistry::global(cx) + .sorted_themes() + .into_iter() + .cloned() + .collect::>(); + let light_themes = themes + .iter() + .filter(|theme| !theme.mode.is_dark()) + .map(|theme| theme.name.clone()) + .collect::>(); + let dark_themes = themes + .iter() + .filter(|theme| theme.mode.is_dark()) + .map(|theme| theme.name.clone()) + .collect::>(); + let follow_system = self.follow_system_theme; + let is_dark_mode = cx.theme().mode.is_dark(); + let light_theme_name = self.light_theme_name.clone(); + let dark_theme_name = self.dark_theme_name.clone(); + let icon = if follow_system { + IconName::Sun + } else if is_dark_mode { + IconName::Moon + } else { + IconName::Sun + }; + + Button::new("theme-dropdown") + .ghost() + .small() + .icon(icon) + .dropdown_menu_with_anchor(Anchor::BottomRight, move |mut menu, window, _| { + menu = menu + .min_w(220.) + .item( + PopupMenuItem::new("Follow System") + .checked(follow_system) + .on_click(window.listener_for(&view, |this, _, window, cx| { + this.set_follow_system_theme(true, window, cx) + })), + ) + .item( + PopupMenuItem::new("Use Light Mode") + .checked(!follow_system && !is_dark_mode) + .on_click(window.listener_for(&view, |this, _, window, cx| { + this.switch_theme_mode(ThemeMode::Light, window, cx) + })), + ) + .item( + PopupMenuItem::new("Use Dark Mode") + .checked(!follow_system && is_dark_mode) + .on_click(window.listener_for(&view, |this, _, window, cx| { + this.switch_theme_mode(ThemeMode::Dark, window, cx) + })), + ) + .separator() + .label("Light Theme"); + + for theme_name in light_themes.clone() { + let checked = theme_name == light_theme_name; + menu = menu.item( + PopupMenuItem::new(theme_name.clone()) + .checked(checked) + .on_click(window.listener_for(&view, move |this, _, window, cx| { + this.apply_theme(theme_name.clone(), window, cx) + })), + ); + } + + menu = menu.separator(); + menu = menu.label("Dark Theme"); + + for theme_name in dark_themes.clone() { + let checked = theme_name == dark_theme_name; + menu = menu.item( + PopupMenuItem::new(theme_name.clone()) + .checked(checked) + .on_click(window.listener_for(&view, move |this, _, window, cx| { + this.apply_theme(theme_name.clone(), window, cx) + })), + ); + } + + menu + }) + } + + fn render_sftp_panel( + &mut self, + _window: &mut Window, + cx: &mut Context, + ) -> impl IntoElement { + let Some(sftp) = self.active_sftp() else { + return v_flex() + .size_full() + .gap_3() + .p_3() + .border_color(cx.theme().border) + .bg(cx.theme().muted) + .child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(cx.theme().primary) + .child("REMOTE FILES"), + ) + .child( + div() + .text_size(px(12.)) + .text_color(cx.theme().muted_foreground) + .child("Open an SSH tab to browse SFTP files."), + ); + }; + + let selected_path = sftp.selected_path.clone(); + let entries = sftp + .entries + .clone() + .into_iter() + .filter(|entry| self.show_hidden_files || !entry.name.starts_with('.')) + .collect::>(); + let status = sftp.status.clone(); + let parent_path = Self::sftp_parent_path(&sftp.current_path); + let view = cx.entity(); + let icon_col_width = px(14.); + let size_col_width = px(96.); + let modified_col_width = px(152.); + + v_flex() + .size_full() + .gap_0() + .border_color(cx.theme().border) + .bg(cx.theme().background) + .child( + h_flex() + .h(px(34.)) + .items_center() + .gap_2() + .px_3() + .border_b_1() + .border_color(cx.theme().border) + .bg(cx.theme().tab_bar) + .child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(cx.theme().primary) + .child("REMOTE FILES"), + ) + .child(div().flex_1()) + .child( + Button::new("sftp-refresh") + .ghost() + .small() + .icon(IconName::ArrowRight) + .label("refresh") + .on_click(cx.listener(|this, _, _, cx| this.refresh_sftp(cx))), + ) + .child( + Button::new("sftp-upload-file") + .ghost() + .small() + .icon(IconName::Plus) + .label("upload file") + .on_click(cx.listener(|this, _, window, cx| { + this.upload_sftp_files(window, cx) + })), + ) + .child( + Button::new("sftp-upload-folder") + .ghost() + .small() + .icon(IconName::Folder) + .label("upload folder") + .on_click(cx.listener(|this, _, window, cx| { + this.upload_sftp_folder(window, cx) + })), + ) + .child( + Checkbox::new("sftp-show-hidden") + .label("hidden") + .checked(self.show_hidden_files) + .tab_stop(false) + .on_click(cx.listener(|this, checked, _, cx| { + this.show_hidden_files = *checked; + cx.notify(); + })), + ), + ) + .child( + h_flex() + .h(px(36.)) + .items_center() + .gap_2() + .px_3() + .border_b_1() + .border_color(cx.theme().border) + .bg(cx.theme().muted) + .child( + Button::new("sftp-up") + .ghost() + .small() + .icon(IconName::ChevronUp) + .on_click(cx.listener(move |this, _, _, cx| { + this.navigate_sftp(parent_path.clone(), cx); + })), + ) + .child(Input::new(&self.sftp_path_input).flex_1().tab_index(0)) + .child(div().flex_none()), + ) + .child( + h_flex() + .h(px(26.)) + .px_3() + .items_center() + .gap_2() + .border_b_1() + .border_color(cx.theme().border) + .bg(cx.theme().muted.opacity(0.8)) + .child( + h_flex() + .flex_1() + .min_w(px(0.)) + .items_center() + .gap_2() + .child(div().w(icon_col_width).flex_none()) + .child( + div() + .flex_1() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child("NAME"), + ), + ) + .child( + div() + .w(size_col_width) + .flex_none() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child("SIZE"), + ) + .child( + div() + .w(modified_col_width) + .flex_none() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child("MODIFIED"), + ) + .child(div().w(px(12.)).flex_none()), + ) + .child( + div() + .flex_1() + .min_h(px(0.)) + .w_full() + .relative() + .child( + uniform_list("remote-files-list", entries.len(), { + let entries = entries.clone(); + let selected_path = selected_path.clone(); + let view = view.clone(); + move |visible_range, window, cx| { + visible_range + .map(|ix| { + let entry = entries[ix].clone(); + let left_row = entry.clone(); + let right_row = entry.clone(); + let remote_path = entry.full_path.clone(); + let is_selected = selected_path + .as_deref() + .map(|path| path == entry.full_path) + .unwrap_or(false); + let bg = if is_selected { + cx.theme().tab_active + } else if ix % 2 == 0 { + cx.theme().background + } else { + cx.theme().muted.opacity(0.45) + }; + let name_color = if entry.is_dir { + cx.theme().primary + } else { + cx.theme().foreground + }; + h_flex() + .w_full() + .h(px(28.)) + .items_center() + .gap_2() + .px_3() + .bg(bg) + .hover(|style| style.bg(cx.theme().muted.opacity(0.8))) + .border_b_1() + .border_color(cx.theme().border.opacity(0.35)) + .on_mouse_down( + MouseButton::Left, + window.listener_for( + &view, + move |this, _, _, cx| { + this.dismiss_sftp_context_menu(cx); + this.select_sftp_entry( + left_row.clone(), + cx, + ); + }, + ), + ) + .on_mouse_down( + MouseButton::Right, + window.listener_for(&view, { + let remote_path = remote_path.clone(); + move |this, event: &MouseDownEvent, _, cx| { + this.mark_sftp_entry_selected( + &right_row.full_path, + cx, + ); + this.open_sftp_context_menu( + remote_path.clone(), + entry.is_dir, + event.position, + cx, + ); + } + }), + ) + .child( + h_flex() + .flex_1() + .min_w(px(0.)) + .items_center() + .gap_2() + .child( + div() + .w(icon_col_width) + .flex_none() + .text_size(px(12.)) + .text_color(name_color) + .child(if entry.is_dir { + "📁" + } else { + "📄" + }), + ) + .child( + div() + .flex_1() + .min_w(px(0.)) + .overflow_hidden() + .text_size(px(12.)) + .text_color(name_color) + .child(entry.name), + ), + ) + .child( + div() + .w(size_col_width) + .flex_none() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child(if entry.is_dir { + "-".to_string() + } else { + format_bytes(entry.size) + }), + ) + .child( + div() + .w(modified_col_width) + .flex_none() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child(format_mtime(entry.modified)), + ) + .child(div().w(px(12.)).flex_none()) + .into_any_element() + }) + .collect::>() + } + }) + .size_full() + .track_scroll(&self.remote_files_scroll_handle), + ) + .child( + div() + .absolute() + .top_0() + .right_0() + .bottom_0() + .w(px(16.)) + .child( + Scrollbar::vertical(&self.remote_files_scroll_handle) + .scrollbar_show(ScrollbarShow::Always), + ), + ), + ) + .child( + h_flex() + .h(px(24.)) + .px_3() + .items_center() + .border_t_1() + .border_color(cx.theme().border) + .bg(cx.theme().tab_bar) + .child( + div() + .min_w(px(0.)) + .overflow_hidden() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child(status), + ), + ) + } + + fn sidebar(&self, cx: &mut Context) -> impl IntoElement { + let sessions = self.config.sessions().to_vec(); + let active_session_id = self.active_session_id().map(ToOwned::to_owned); + let active_kind = match self.active_kind() { + Some(TabKind::Local) => "local", + Some(TabKind::Ssh) => "ssh", + None => "idle", + }; + let connection_color = self.active_connection_color(cx); + + v_flex() + .gap_4() + .w_full() + .h_full() + .min_w(px(0.)) + .p_4() + .border_r_1() + .border_color(cx.theme().sidebar_border) + .bg(cx.theme().sidebar) + .overflow_y_scrollbar() + .child( + h_flex() + .items_center() + .gap_3() + .child( + div() + .w(px(10.)) + .h(px(10.)) + .rounded_full() + .bg(connection_color), + ) + .child( + v_flex() + .gap_1() + .child( + div() + .font_weight(FontWeight::BOLD) + .text_size(px(20.)) + .text_color(cx.theme().primary) + .child("ashell"), + ) + .child( + div() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child(format!("{} / {}", active_kind, self.active_title())), + ), + ), + ) + .child( + v_flex() + .gap_2() + .child( + h_flex() + .items_center() + .child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(cx.theme().primary) + .child("SYSTEM"), + ) + .child(div().flex_1()) + .child( + div() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child(self.system_target_label()), + ), + ) + .when_some(self.system_status.clone(), |this, text| { + this.child( + div() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child(text), + ) + }) + .child(self.system_card( + "CPU", + self.system.cpu_percent, + "global load".into(), + cx.theme().chart_1, + cx, + )) + .child(self.system_card( + "MEM", + self.system.mem_percent, + self.system.mem_detail.clone(), + cx.theme().chart_2, + cx, + )) + .child(self.system_card( + "SWAP", + self.system.swap_percent, + self.system.swap_detail.clone(), + cx.theme().chart_3, + cx, + )) + .child(self.network_card(cx)) + .child( + v_flex() + .gap_2() + .p_2() + .rounded_md() + .border_1() + .border_color(cx.theme().border) + .bg(cx.theme().muted) + .child( + div() + .text_size(px(11.)) + .text_color(cx.theme().chart_5) + .child("DISK"), + ) + .children({ + let mut disk_elements = Vec::new(); + for disk in self.system.disks.clone() { + disk_elements.push(self.disk_row(disk, cx)); + } + disk_elements + }), + ), + ) + .child( + Button::new("open-ssh-panel") + .primary() + .label("+ ssh") + .on_click( + cx.listener(|this, _, window, cx| this.open_new_ssh_dialog(window, cx)), + ), + ) + .child( + v_flex() + .gap_2() + .child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::SEMIBOLD) + .text_color(cx.theme().primary) + .child("SAVED"), + ) + .child( + v_flex() + .max_h(px(220.)) + .overflow_scrollbar() + .gap_2() + .children(sessions.into_iter().enumerate().map(|(ix, session)| { + let connect_id = session.id.clone(); + let edit_id = session.id.clone(); + let delete_id = session.id.clone(); + let is_active = + active_session_id.as_deref() == Some(session.id.as_str()); + let name = session.name.clone(); + let detail = self.session_detail(&session); + div() + .id(("saved-connect", ix)) + .w_full() + .p_2() + .rounded_md() + .border_1() + .border_color(if is_active { + cx.theme().primary + } else { + cx.theme().border + }) + .bg(if is_active { + cx.theme().tab_active + } else { + cx.theme().muted + }) + .cursor_pointer() + .hover(|this| this.bg(cx.theme().secondary)) + .on_mouse_down( + MouseButton::Left, + cx.listener(move |this, _, _, cx| { + this.connect_saved_session(connect_id.clone(), cx) + }), + ) + .context_menu({ + let view = cx.entity(); + move |menu, window, _| { + let edit_value = edit_id.clone(); + let delete_value = delete_id.clone(); + menu.item(PopupMenuItem::new("Edit").on_click( + window.listener_for( + &view, + move |this, _, window, cx| { + this.edit_saved_session( + edit_value.clone(), + window, + cx, + ) + }, + ), + )) + .item( + PopupMenuItem::new("Delete").on_click( + window.listener_for( + &view, + move |this, _, _, cx| { + this.remove_saved_session( + delete_value.clone(), + cx, + ) + }, + ), + ), + ) + } + }) + .child( + v_flex() + .gap_1() + .child( + div() + .text_size(px(12.)) + .font_weight(FontWeight::SEMIBOLD) + .child(name), + ) + .child( + div() + .text_size(px(11.)) + .text_color(cx.theme().muted_foreground) + .child(detail), + ), + ) + })), + ), + ) + } +} + +impl Render for Ashell { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + if self + .active_tab + .as_ref() + .is_some_and(|active_id| !self.tabs.iter().any(|tab| &tab.id == active_id)) + { + self.active_tab = self.tabs.first().map(|tab| tab.id.clone()); + self.remote_sample_in_flight = false; + self.request_active_system_snapshot(); + } + self.sync_sftp_path_input(window, cx); + self.sync_terminal_size(window, cx); + if let Some(new_display_offset) = self.terminal_scrollbar.future_display_offset.take() { + if let Some(active_id) = self.active_tab.clone() { + if let Some(tab) = self.tabs.iter_mut().find(|tab| tab.id == active_id) { + let current = tab.render_snapshot().display_offset; + match new_display_offset.cmp(¤t) { + std::cmp::Ordering::Greater => { + tab.scroll_up_by(new_display_offset - current) + } + std::cmp::Ordering::Less => { + tab.scroll_down_by(current - new_display_offset) + } + std::cmp::Ordering::Equal => {} + } + } + } + } + let connection_color = self.active_connection_color(cx); + let terminal_snapshot = self.active_snapshot(); + let active_tab_index = self + .active_tab + .as_ref() + .and_then(|active_id| self.tabs.iter().position(|tab| &tab.id == active_id)); + if let Some(snapshot) = terminal_snapshot.as_ref() { + self.terminal_scrollbar + .update(snapshot, px(self.terminal_line_height())); + } + + div() + .size_full() + .bg(cx.theme().background) + .text_color(cx.theme().foreground) + .font_family(APP_FONT_FAMILY) + .child( + h_resizable("ashell-workspace") + .with_state(&self.workspace_panels) + .child( + resizable_panel() + .size(px(SIDEBAR_WIDTH)) + .size_range(px(240.)..px(520.)) + .flex_none() + .child(self.sidebar(cx)), + ) + .child( + resizable_panel().child( + v_flex() + .size_full() + .relative() + .overflow_hidden() + .child( + v_flex() + .h(px(60.)) + .w_full() + .flex_none() + .border_b_1() + .border_color(cx.theme().border) + .bg(cx.theme().tab_bar) + .child( + h_flex() + .h(px(48.)) + .w_full() + .items_center() + .gap_2() + .px_3() + .child( + div() + .flex_1() + .min_w(px(0.)) + .h_full() + .overflow_x_scrollbar() + .child( + TabBar::new("ashell-tab-bar") + .selected_index(active_tab_index.unwrap_or(0)) + .children(self.tabs.iter().enumerate().map(|(ix, tab)| { + let id = tab.id.clone(); + let close_id = tab.id.clone(); + Tab::new() + .label(tab.title.clone()) + .on_click(cx.listener(move |this, _, window, cx| { + this.activate_tab(id.clone(), window, cx) + })) + .suffix( + Button::new(("tab-close", ix)) + .ghost() + .xsmall() + .icon(IconName::Close) + .on_mouse_down(MouseButton::Left, |_, window, cx| { + window.prevent_default(); + cx.stop_propagation(); + }) + .on_click(cx.listener(move |this, _, window, cx| { + window.prevent_default(); + cx.stop_propagation(); + this.close_tab(close_id.clone(), cx) + })), + ) + })) + .last_empty_space(div().w_3()) + .w_full() + .h_full(), + ), + ) + .child( + h_flex() + .flex_none() + .items_center() + .gap_2() + .child( + Button::new("open-selector") + .ghost() + .small() + .icon(IconName::Plus) + .on_click(cx.listener(|this, _, window, cx| this.show_selector_dialog(window, cx))), + ) + .child( + Button::new("open-settings") + .ghost() + .small() + .icon(IconName::Settings2) + .on_click(cx.listener(|this, _, window, cx| this.show_settings_dialog(window, cx))), + ) + .child(self.theme_dropdown(cx)) + .child( + div() + .w(px(10.)) + .h(px(10.)) + .rounded_full() + .bg(connection_color), + ), + ), + ), + ) + .child( + v_resizable("ashell-main-panels") + .with_state(&self.body_panels) + .child( + resizable_panel().child( + div() + .size_full() + .p_4() + .bg(cx.theme().background) + .text_color(cx.theme().foreground) + .font_family(APP_FONT_FAMILY) + .text_size(px(13.)) + .line_height(px(18.)) + .overflow_hidden() + .track_focus(&self.focus_handle) + .key_context(TERMINAL_KEY_CONTEXT) + .on_mouse_down(MouseButton::Left, cx.listener(Self::focus_terminal)) + .on_mouse_move(cx.listener(Self::on_terminal_mouse_move)) + .on_mouse_up(MouseButton::Left, cx.listener(Self::on_terminal_mouse_up)) + .on_key_down(cx.listener(Self::on_terminal_key_down)) + .on_action(cx.listener(Self::on_terminal_tab_action)) + .on_action(cx.listener(Self::on_terminal_backtab_action)) + .on_scroll_wheel(cx.listener(Self::on_terminal_scroll)) + .child(match terminal_snapshot.clone() { + None => self.render_home_page(cx).into_any_element(), + Some(snapshot) => h_flex() + .w_full() + .h_full() + .gap_2() + .child( + div() + .size_full() + .on_prepaint({ + let view = cx.entity().clone(); + move |bounds, _window, cx| { + let _ = view.update(cx, |this, _| { + this.terminal_bounds = Some(bounds); + }); + } + }) + .child( + TerminalElement::new( + cx.entity(), + self.focus_handle.clone(), + snapshot, + self.terminal_marked_text.clone(), + APP_FONT_FAMILY, + px(self.terminal_font_size), + px(self.terminal_line_height()), + px(self.terminal_cell_width()), + ), + ) + .vertical_scrollbar(&self.terminal_scrollbar) + .into_any_element(), + ) + .into_any_element(), + }), + ), + ) + .child( + resizable_panel() + .size(px(248.)) + .size_range(px(180.)..px(420.)) + .child(self.render_sftp_panel(window, cx)), + ), + ), + ), + ), + ) + .children(Root::render_dialog_layer(window, cx)) + .children(Root::render_sheet_layer(window, cx)) + .when_some(self.sftp_context_menu.clone(), |this, menu| { + let label = if menu.is_dir { + "Download Folder" + } else { + "Download" + }; + this.child( + div() + .absolute() + .top_0() + .left_0() + .right_0() + .bottom_0() + .on_mouse_down(MouseButton::Left, cx.listener(|this, _, _, cx| { + this.dismiss_sftp_context_menu(cx); + })) + .on_mouse_down(MouseButton::Right, cx.listener(|this, _, _, cx| { + this.dismiss_sftp_context_menu(cx); + })) + .child( + div() + .absolute() + .left(menu.position.x) + .top(menu.position.y) + .w(px(172.)) + .p_1() + .rounded_md() + .border_1() + .border_color(cx.theme().border) + .bg(cx.theme().popover) + .shadow_lg() + .on_mouse_down(MouseButton::Left, |_, window, cx| { + window.prevent_default(); + cx.stop_propagation(); + }) + .on_mouse_down(MouseButton::Right, |_, window, cx| { + window.prevent_default(); + cx.stop_propagation(); + }) + .child( + Button::new("sftp-context-download") + .ghost() + .w_full() + .justify_start() + .label(label) + .on_click(cx.listener(|this, _, window, cx| { + this.trigger_sftp_context_download(window, cx); + })), + ), + ), + ) + }) + .when_some(self.connection_progress.clone(), |this, progress| { + this.child( + div() + .absolute() + .top_0() + .left_0() + .right_0() + .bottom_0() + .bg(Hsla { + h: 0.0, + s: 0.0, + l: 0.0, + a: 0.48, + }) + .flex() + .items_center() + .justify_center() + .child( + div() + .w(px(420.)) + .p_5() + .rounded_lg() + .border_1() + .border_color(cx.theme().border) + .bg(cx.theme().popover) + .shadow_lg() + .child( + v_flex() + .gap_4() + .child( + Button::new("ssh-connect-progress") + .primary() + .loading(!progress.failed) + .label(progress.title.clone()), + ) + .child( + div() + .max_h(px(220.)) + .overflow_y_scrollbar() + .child( + v_flex() + .gap_2() + .children(progress.lines.iter().cloned().map(|line| { + div() + .text_size(px(12.)) + .text_color(if progress.failed { + cx.theme().danger + } else { + cx.theme().muted_foreground + }) + .child(line) + })), + ), + ) + .when(progress.failed, |this| { + this.child( + h_flex() + .justify_end() + .gap_2() + .child( + Button::new("ssh-connect-progress-retry") + .primary() + .label("retry") + .on_click(cx.listener(|this, _, _, cx| { + this.retry_connection_progress(cx) + })), + ) + .child( + Button::new("ssh-connect-progress-close") + .label("cancel") + .on_click(cx.listener(|this, _, _, cx| { + this.cancel_connection_progress(cx) + })), + ), + ) + }), + ), + ), + ) + }) + } +} +fn load_fonts(cx: &mut App) -> Result<()> { + let regular = + Cow::Borrowed(include_bytes!("../assets/fonts/MapleMono-NF-CN-Regular.ttf").as_slice()); + let bold = Cow::Borrowed(include_bytes!("../assets/fonts/MapleMono-NF-CN-Bold.ttf").as_slice()); + cx.text_system() + .add_fonts(vec![regular, bold]) + .context("load Maple Mono NF CN fonts")?; + set_theme_font_names(cx.global_mut::()); + Ok(()) +} + +fn load_embedded_themes(cx: &mut App) { + let registry = ThemeRegistry::global_mut(cx); + for theme_json in EMBEDDED_THEME_JSONS { + if let Err(err) = registry.load_themes_from_str(theme_json) { + tracing::warn!("failed to load embedded theme: {err:#}"); + } + } +} + +fn set_theme_font_names(theme: &mut Theme) { + theme.font_family = APP_FONT_FAMILY.into(); + theme.mono_font_family = APP_FONT_FAMILY.into(); +} + +#[cfg(target_os = "macos")] +fn sync_macos_launch_environment() { + let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/zsh".to_string()); + let Ok(output) = Command::new(&shell).args(["-l", "-c", "env -0"]).output() else { + return; + }; + if !output.status.success() { + return; + } + + for entry in output.stdout.split(|b| *b == 0) { + if entry.is_empty() { + continue; + } + let Some(eq) = entry.iter().position(|b| *b == b'=') else { + continue; + }; + let Ok(key) = std::str::from_utf8(&entry[..eq]) else { + continue; + }; + let Ok(value) = std::str::from_utf8(&entry[eq + 1..]) else { + continue; + }; + + let should_import = matches!( + key, + "PATH" + | "MANPATH" + | "INFOPATH" + | "LANG" + | "LC_ALL" + | "LC_CTYPE" + | "SHELL" + | "HOME" + | "HOMEBREW_PREFIX" + | "HOMEBREW_CELLAR" + | "HOMEBREW_REPOSITORY" + ) || key.starts_with("LC_"); + + if should_import { + unsafe { + std::env::set_var(key, value); + } + } + } +} + +#[cfg(not(target_os = "macos"))] +fn sync_macos_launch_environment() {} + +fn open_main_window(cx: &mut App) { + let window_options = WindowOptions::default(); + cx.open_window(window_options, |window, cx| { + window.activate_window(); + window.set_window_title("ashell"); + Theme::sync_system_appearance(Some(window), cx); + let view = cx.new(|cx| Ashell::new(window, cx)); + cx.new(|cx| Root::new(view, window, cx)) + }) + .expect("failed to open window"); +} + +fn main() { + sync_macos_launch_environment(); + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), + ) + .init(); + + let app = gpui_platform::application() + .with_assets(Assets) + .with_quit_mode(QuitMode::Explicit); + app.on_reopen(|cx| { + if cx.windows().is_empty() { + open_main_window(cx); + } + }); + app.run(move |cx| { + gpui_component::init(cx); + cx.bind_keys([ + KeyBinding::new("tab", TerminalTabKey, Some(TERMINAL_KEY_CONTEXT)), + KeyBinding::new("shift-tab", TerminalBacktabKey, Some(TERMINAL_KEY_CONTEXT)), + ]); + load_embedded_themes(cx); + if let Err(err) = load_fonts(cx) { + tracing::warn!("failed to load embedded fonts: {err:#}"); + } + open_main_window(cx); + }); +} diff --git a/src/sftp.rs b/src/sftp.rs new file mode 100644 index 0000000..66f83ca --- /dev/null +++ b/src/sftp.rs @@ -0,0 +1,909 @@ +use std::{ + fs, + path::{Path, PathBuf}, + sync::Arc, +}; + +use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; +use chrono::{DateTime, TimeZone, Utc}; +use directories::BaseDirs; +use flate2::read::GzDecoder; +use russh::{ + client::{self, Handler}, + keys::{decode_secret_key, key::PrivateKeyWithHashAlg, load_secret_key, HashAlg, PrivateKey}, + Disconnect, +}; +use russh_sftp::client::SftpSession; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + sync::mpsc::{self, UnboundedReceiver, UnboundedSender}, + task::JoinHandle, +}; +use uuid::Uuid; +use walkdir::WalkDir; +use zip::read::ZipArchive; + +use crate::{ + config::{AuthMethod, Session}, + terminal::BackendEvent, +}; + +#[derive(Debug, Clone)] +pub struct RemoteEntry { + pub name: String, + pub full_path: String, + pub is_dir: bool, + pub size: u64, + pub modified: u32, +} + +#[derive(Debug, Clone)] +pub struct PreviewData { + pub path: String, + pub title: String, + pub body: String, + pub is_binary: bool, +} + +#[derive(Debug)] +enum SftpCommand { + ListDir(String), + Preview(String), + Download { remote: String, local_dir: String }, + UploadPaths { locals: Vec, remote_dir: String }, + Close, +} + +pub struct SftpHandle { + commands: UnboundedSender, + #[allow(dead_code)] + join: Option>, +} + +impl Clone for SftpHandle { + fn clone(&self) -> Self { + Self { + commands: self.commands.clone(), + join: None, + } + } +} + +impl SftpHandle { + pub fn list_dir(&self, path: String) { + let _ = self.commands.send(SftpCommand::ListDir(path)); + } + + pub fn preview(&self, path: String) { + let _ = self.commands.send(SftpCommand::Preview(path)); + } + + pub fn download(&self, remote: String, local_dir: String) { + let _ = self.commands.send(SftpCommand::Download { remote, local_dir }); + } + + pub fn upload_paths(&self, locals: Vec, remote_dir: String) { + let _ = self.commands.send(SftpCommand::UploadPaths { locals, remote_dir }); + } + + pub fn close(&self) { + let _ = self.commands.send(SftpCommand::Close); + } +} + +pub fn spawn_sftp( + runtime: &tokio::runtime::Handle, + tab_id: String, + session: Session, + events: std::sync::mpsc::Sender, +) -> SftpHandle { + let (cmd_tx, cmd_rx) = mpsc::unbounded_channel(); + let join = runtime.spawn(async move { + if let Err(err) = run_sftp(tab_id.clone(), session, cmd_rx, events.clone()).await { + let _ = events.send(BackendEvent::SftpStatus { + tab_id, + text: format!("sftp error: {err:#}"), + }); + } + }); + SftpHandle { + commands: cmd_tx, + join: Some(join), + } +} + +async fn run_sftp( + tab_id: String, + session: Session, + mut commands: UnboundedReceiver, + events: std::sync::mpsc::Sender, +) -> Result<()> { + let _ = events.send(BackendEvent::SftpStatus { + tab_id: tab_id.clone(), + text: "sftp connecting...".into(), + }); + + let handle = connect_and_authenticate(&session).await?; + let channel = handle + .channel_open_session() + .await + .context("open sftp channel")?; + channel + .request_subsystem(true, "sftp") + .await + .context("request sftp subsystem")?; + let sftp = SftpSession::new(channel.into_stream()) + .await + .context("sftp handshake")?; + + let home = sftp.canonicalize(".").await.unwrap_or_else(|_| "/".to_string()); + emit_entries(&events, &tab_id, &sftp, &home).await?; + + while let Some(command) = commands.recv().await { + match command { + SftpCommand::Close => break, + SftpCommand::ListDir(path) => { + if let Err(err) = emit_entries(&events, &tab_id, &sftp, &path).await { + let _ = events.send(BackendEvent::SftpStatus { + tab_id: tab_id.clone(), + text: format!("list failed: {err:#}"), + }); + } + } + SftpCommand::Preview(path) => match preview_impl(&sftp, &path).await { + Ok(preview) => { + let _ = events.send(BackendEvent::SftpPreview { + tab_id: tab_id.clone(), + preview, + }); + } + Err(err) => { + let _ = events.send(BackendEvent::SftpStatus { + tab_id: tab_id.clone(), + text: format!("preview failed: {err:#}"), + }); + } + }, + SftpCommand::Download { remote, local_dir } => { + let base = base_name(&remote); + let _ = events.send(BackendEvent::SftpStatus { + tab_id: tab_id.clone(), + text: format!("downloading {base}..."), + }); + match download_path_impl(&handle, &sftp, &remote, Path::new(&local_dir)).await { + Ok(summary) => { + let _ = events.send(BackendEvent::SftpStatus { + tab_id: tab_id.clone(), + text: summary, + }); + } + Err(err) => { + let _ = events.send(BackendEvent::SftpStatus { + tab_id: tab_id.clone(), + text: format!("download failed: {err:#}"), + }); + } + } + } + SftpCommand::UploadPaths { locals, remote_dir } => { + let _ = events.send(BackendEvent::SftpStatus { + tab_id: tab_id.clone(), + text: "uploading...".into(), + }); + match upload_paths_impl(&sftp, &locals, &remote_dir).await { + Ok(summary) => { + let _ = events.send(BackendEvent::SftpStatus { + tab_id: tab_id.clone(), + text: summary, + }); + let _ = emit_entries(&events, &tab_id, &sftp, &remote_dir).await; + } + Err(err) => { + let _ = events.send(BackendEvent::SftpStatus { + tab_id: tab_id.clone(), + text: format!("upload failed: {err:#}"), + }); + } + } + } + } + } + + let _ = handle.disconnect(Disconnect::ByApplication, "bye", "").await; + Ok(()) +} + +async fn emit_entries( + events: &std::sync::mpsc::Sender, + tab_id: &str, + sftp: &SftpSession, + path: &str, +) -> Result<()> { + let entries = list_dir_impl(sftp, path).await?; + let _ = events.send(BackendEvent::SftpEntries { + tab_id: tab_id.to_string(), + path: path.to_string(), + entries, + }); + let _ = events.send(BackendEvent::SftpStatus { + tab_id: tab_id.to_string(), + text: path.to_string(), + }); + Ok(()) +} + +async fn connect_and_authenticate(session: &Session) -> Result> { + let config = Arc::new(client::Config { + inactivity_timeout: Some(std::time::Duration::from_secs(600)), + ..Default::default() + }); + let addr = format!("{}:{}", session.host, session.port); + let mut handle = client::connect(config, addr.as_str(), SftpClientHandler) + .await + .with_context(|| format!("connect {addr} failed"))?; + + let authed = match session.auth { + AuthMethod::Password => handle + .authenticate_password(&session.user, &session.password) + .await + .context("password authentication failed")?, + AuthMethod::Key => { + let keypair = load_session_private_key(session)?; + let key = private_key_with_alg(keypair).context("invalid private key")?; + handle + .authenticate_publickey(&session.user, key) + .await + .context("public key authentication failed")? + } + }; + + if !authed { + let _ = handle + .disconnect(Disconnect::ByApplication, "auth failed", "") + .await; + return Err(anyhow!( + "authentication failed: server rejected {} authentication for {}@{}:{}", + match session.auth { + AuthMethod::Password => "password", + AuthMethod::Key => "public key", + }, + session.user, + session.host, + session.port + )); + } + + Ok(handle) +} + +fn load_session_private_key(session: &Session) -> Result { + let inline_key = normalize_inline_private_key(&session.private_key_inline); + let key_path = expand_key_path(session.private_key_path.trim()); + let has_inline = !inline_key.is_empty(); + let has_path = key_path.is_some(); + + if !has_inline && !has_path { + return Err(anyhow!("private key content or path is required")); + } + + let mut errors = Vec::new(); + + if has_inline { + match decode_secret_key(&inline_key, None) { + Ok(key) => return Ok(key), + Err(err) => errors.push(format!("decode private key content: {err}")), + } + } + + if let Some(path) = key_path { + match load_secret_key(path.as_path(), None) { + Ok(key) => return Ok(key), + Err(err) => errors.push(format!("load key {}: {err}", path.display())), + } + } + + Err(anyhow!(errors.join("; "))) +} + +fn private_key_with_alg(keypair: PrivateKey) -> Result { + let hash_alg = if keypair.algorithm().is_rsa() { + Some(HashAlg::Sha512) + } else { + None + }; + Ok( + PrivateKeyWithHashAlg::new(Arc::new(keypair.clone()), hash_alg) + .or_else(|_| PrivateKeyWithHashAlg::new(Arc::new(keypair), Some(HashAlg::Sha256)))?, + ) +} + +fn normalize_inline_private_key(value: &str) -> String { + let mut normalized = value + .trim() + .replace("\\r\\n", "\n") + .replace("\\n", "\n") + .replace("\r\n", "\n"); + if !normalized.ends_with('\n') { + normalized.push('\n'); + } + normalized +} + +fn expand_key_path(value: &str) -> Option { + if value.is_empty() { + return None; + } + if value == "~" { + return BaseDirs::new().map(|dirs| dirs.home_dir().to_path_buf()); + } + if let Some(rest) = value.strip_prefix("~/") { + return BaseDirs::new().map(|dirs| dirs.home_dir().join(rest)); + } + Some(Path::new(value).to_path_buf()) +} + +fn base_name(path: &str) -> String { + let sep = |c: char| c == '/' || c == '\\'; + path.trim_end_matches(sep) + .rsplit(sep) + .next() + .unwrap_or(path) + .to_string() +} + +fn join_remote(parent: &str, child: &str) -> String { + if parent == "/" { + format!("/{child}") + } else { + format!("{}/{}", parent.trim_end_matches('/'), child) + } +} + +fn strip_archive_suffix(name: &str) -> &str { + for suffix in [".tar.gz", ".tgz", ".zip", ".tar"] { + if let Some(stripped) = name.strip_suffix(suffix) { + return stripped; + } + } + name +} + +fn format_bytes(bytes: u64) -> String { + if bytes < 1024 { + format!("{bytes} B") + } else if bytes < 1024 * 1024 { + format!("{:.1} KB", bytes as f64 / 1024.0) + } else if bytes < 1024 * 1024 * 1024 { + format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0)) + } else { + format!("{:.2} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0)) + } +} + +pub fn format_mtime(ts: u32) -> String { + let dt: DateTime = Utc + .timestamp_opt(ts as i64, 0) + .single() + .unwrap_or_else(Utc::now); + dt.format("%Y-%m-%d %H:%M").to_string() +} + +async fn list_dir_impl(sftp: &SftpSession, path: &str) -> Result> { + let raw = sftp + .read_dir(path) + .await + .with_context(|| format!("read_dir {path} failed"))?; + + let mut entries = raw + .into_iter() + .filter(|entry| { + let name = entry.file_name(); + name != "." && name != ".." + }) + .map(|entry| { + let name = entry.file_name().to_string(); + let full_path = join_remote(path, &name); + let meta = entry.metadata(); + let permissions = meta.permissions.unwrap_or(0); + let is_dir = (permissions & 0o170_000) == 0o040_000; + let size = meta.size.unwrap_or(0); + let modified = meta.mtime.unwrap_or(0); + RemoteEntry { + name, + full_path, + is_dir, + size, + modified, + } + }) + .collect::>(); + + entries.sort_by(|a, b| match (a.is_dir, b.is_dir) { + (true, false) => std::cmp::Ordering::Less, + (false, true) => std::cmp::Ordering::Greater, + _ => a.name.to_lowercase().cmp(&b.name.to_lowercase()), + }); + + Ok(entries) +} + +async fn preview_impl(sftp: &SftpSession, path: &str) -> Result { + let metadata = sftp.metadata(path).await.with_context(|| format!("metadata {path}"))?; + let is_dir = metadata + .permissions + .map(|mode| (mode & 0o170_000) == 0o040_000) + .unwrap_or(false); + + if is_dir { + let entries = list_dir_impl(sftp, path).await?; + let mut lines = vec![format!("Directory: {path}"), String::new()]; + for entry in entries.into_iter().take(200) { + let kind = if entry.is_dir { "dir " } else { "file" }; + lines.push(format!("{kind} {}", entry.name)); + } + return Ok(PreviewData { + path: path.to_string(), + title: base_name(path), + body: lines.join("\n"), + is_binary: false, + }); + } + + let mut remote_file = sftp.open(path).await.with_context(|| format!("open remote {path}"))?; + let mut buffer = vec![0u8; 128 * 1024]; + let read = remote_file.read(&mut buffer).await.context("read preview bytes")?; + buffer.truncate(read); + + let nul_ratio = if buffer.is_empty() { + 0.0 + } else { + buffer.iter().filter(|byte| **byte == 0).count() as f32 / buffer.len() as f32 + }; + let is_binary = nul_ratio > 0.01; + let body = if is_binary { + format!( + "Binary file\npath: {path}\nsize: {}\npreview: unavailable in-app", + format_bytes(metadata.size.unwrap_or(0)), + ) + } else { + String::from_utf8_lossy(&buffer).into_owned() + }; + + Ok(PreviewData { + path: path.to_string(), + title: base_name(path), + body, + is_binary, + }) +} + +async fn download_path_impl( + handle: &russh::client::Handle, + sftp: &SftpSession, + remote: &str, + local_dir: &Path, +) -> Result { + tokio::fs::create_dir_all(local_dir) + .await + .with_context(|| format!("create {}", local_dir.display()))?; + + let metadata = sftp.metadata(remote).await.with_context(|| format!("metadata {remote}"))?; + let is_dir = metadata + .permissions + .map(|mode| (mode & 0o170_000) == 0o040_000) + .unwrap_or(false); + + if is_dir { + let local_archive = local_dir.join(format!( + ".ashell-{}-{}.tar.gz", + base_name(remote), + Uuid::new_v4() + )); + let extracted_to = download_remote_directory_archive(handle, sftp, remote, &local_archive).await?; + return Ok(format!("downloaded folder to {}", extracted_to.display())); + } + + let local_path = local_dir.join(base_name(remote)); + download_file_impl(sftp, remote, &local_path).await?; + Ok(format!("downloaded file to {}", local_path.display())) +} + +async fn download_dir_recursive(sftp: &SftpSession, remote_dir: &str, local_dir: &Path) -> Result<()> { + tokio::fs::create_dir_all(local_dir) + .await + .with_context(|| format!("create {}", local_dir.display()))?; + let entries = list_dir_impl(sftp, remote_dir).await?; + for entry in entries { + let local_path = local_dir.join(&entry.name); + if entry.is_dir { + Box::pin(download_dir_recursive(sftp, &entry.full_path, &local_path)).await?; + } else { + download_file_impl(sftp, &entry.full_path, &local_path).await?; + let _ = maybe_extract_archive(&local_path).await; + } + } + Ok(()) +} + +async fn download_remote_directory_archive( + handle: &russh::client::Handle, + sftp: &SftpSession, + remote_dir: &str, + local_archive: &Path, +) -> Result { + let remote_archive = format!("/tmp/ashell-{}-{}.tar.gz", base_name(remote_dir), Uuid::new_v4()); + create_remote_archive(handle, remote_dir, &remote_archive).await?; + let local_extract_root = local_archive + .parent() + .unwrap_or_else(|| Path::new(".")) + .join(base_name(remote_dir)); + + let archive_download = async { + download_file_impl(sftp, &remote_archive, local_archive).await?; + extract_archive_to(local_archive, local_archive.parent().unwrap_or_else(|| Path::new("."))).await?; + tokio::fs::remove_file(local_archive) + .await + .with_context(|| format!("remove {}", local_archive.display()))?; + Ok::(local_extract_root) + } + .await; + + let cleanup_result = remove_remote_path(handle, &remote_archive).await; + + let extracted_to = archive_download?; + if let Err(err) = cleanup_result { + tracing::warn!("failed to clean remote archive {remote_archive}: {err:#}"); + } + + Ok(extracted_to) +} + +async fn download_file_impl(sftp: &SftpSession, remote: &str, local: &Path) -> Result<()> { + let mut remote_file = sftp.open(remote).await.with_context(|| format!("open remote {remote}"))?; + let mut local_file = tokio::fs::File::create(local) + .await + .with_context(|| format!("create local {}", local.display()))?; + + let mut buffer = vec![0u8; 64 * 1024]; + loop { + let read = remote_file.read(&mut buffer).await.context("read remote file")?; + if read == 0 { + break; + } + local_file + .write_all(&buffer[..read]) + .await + .with_context(|| format!("write {}", local.display()))?; + } + local_file.flush().await.context("flush local file")?; + Ok(()) +} + +async fn upload_paths_impl(sftp: &SftpSession, locals: &[String], remote_dir: &str) -> Result { + create_remote_dir_all(sftp, remote_dir).await?; + let mut file_count = 0usize; + let mut folder_count = 0usize; + for local in locals { + let path = PathBuf::from(local); + if path.is_dir() { + upload_directory_impl(sftp, &path, remote_dir).await?; + folder_count += 1; + } else { + upload_file_to_dir_impl(sftp, &path, remote_dir).await?; + file_count += 1; + } + } + let summary = match (file_count, folder_count) { + (1, 0) => "uploaded file".to_string(), + (0, 1) => "uploaded folder".to_string(), + (files, 0) => format!("uploaded {files} files"), + (0, folders) => format!("uploaded {folders} folders"), + (files, folders) => format!("uploaded {files} files and {folders} folders"), + }; + Ok(summary) +} + +async fn upload_directory_impl(sftp: &SftpSession, local_dir: &Path, remote_parent: &str) -> Result<()> { + let root_name = local_dir + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| anyhow!("invalid folder name: {}", local_dir.display()))?; + let remote_root = join_remote(remote_parent, root_name); + create_remote_dir_all(sftp, &remote_root).await?; + + for entry in WalkDir::new(local_dir) { + let entry = entry?; + let path = entry.path(); + if path == local_dir { + continue; + } + let relative = path.strip_prefix(local_dir)?; + let remote_path = if relative.as_os_str().is_empty() { + remote_root.clone() + } else { + let rel = relative + .components() + .map(|component| component.as_os_str().to_string_lossy().to_string()) + .collect::>() + .join("/"); + join_remote(&remote_root, &rel) + }; + if path.is_dir() { + create_remote_dir_all(sftp, &remote_path).await?; + } else { + if let Some(parent) = Path::new(&remote_path).parent() { + let parent_remote = parent.to_string_lossy().replace('\\', "/"); + create_remote_dir_all(sftp, &parent_remote).await?; + } + upload_file_impl(sftp, path, &remote_path).await?; + } + } + + Ok(()) +} + +async fn upload_file_to_dir_impl(sftp: &SftpSession, local_file: &Path, remote_dir: &str) -> Result<()> { + let file_name = local_file + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| anyhow!("invalid file name: {}", local_file.display()))?; + let remote_path = join_remote(remote_dir, file_name); + upload_file_impl(sftp, local_file, &remote_path).await +} + +async fn upload_file_impl(sftp: &SftpSession, local_file: &Path, remote_path: &str) -> Result<()> { + let mut local = tokio::fs::File::open(local_file) + .await + .with_context(|| format!("open local {}", local_file.display()))?; + let mut remote = sftp + .create(remote_path) + .await + .with_context(|| format!("create remote {remote_path}"))?; + + let mut buffer = vec![0u8; 64 * 1024]; + loop { + let read = local.read(&mut buffer).await.context("read local file")?; + if read == 0 { + break; + } + remote + .write_all(&buffer[..read]) + .await + .with_context(|| format!("write remote {remote_path}"))?; + } + remote.flush().await.context("flush remote file")?; + Ok(()) +} + +async fn create_remote_dir_all(sftp: &SftpSession, remote_dir: &str) -> Result<()> { + if remote_dir.is_empty() || remote_dir == "/" { + return Ok(()); + } + + let mut current = String::from("/"); + for segment in remote_dir.split('/').filter(|segment| !segment.is_empty()) { + current = join_remote(¤t, segment); + let _ = sftp.create_dir(¤t).await; + } + Ok(()) +} + +async fn create_remote_archive( + handle: &russh::client::Handle, + remote_dir: &str, + remote_archive: &str, +) -> Result<()> { + let remote_dir = remote_dir.trim_end_matches('/'); + let parent = remote_parent(remote_dir); + let name = base_name(remote_dir); + let command = format!( + "tar -C {} -czf {} {}", + shell_quote(&parent), + shell_quote(remote_archive), + shell_quote(&name), + ); + exec_remote_command(handle, &command) + .await + .with_context(|| format!("archive remote directory {remote_dir}"))?; + Ok(()) +} + +async fn remove_remote_path( + handle: &russh::client::Handle, + remote_path: &str, +) -> Result<()> { + let command = format!("rm -f {}", shell_quote(remote_path)); + exec_remote_command(handle, &command) + .await + .with_context(|| format!("remove remote temporary file {remote_path}"))?; + Ok(()) +} + +async fn exec_remote_command( + handle: &russh::client::Handle, + command: &str, +) -> Result<()> { + let mut channel = handle + .channel_open_session() + .await + .context("open remote exec session")?; + channel + .exec(true, command) + .await + .with_context(|| format!("exec remote command: {command}"))?; + + let mut stderr = Vec::new(); + let mut stdout = Vec::new(); + let mut exit_status = None; + while let Some(msg) = channel.wait().await { + match msg { + russh::ChannelMsg::Data { data } => stdout.extend_from_slice(&data), + russh::ChannelMsg::ExtendedData { data, .. } => stderr.extend_from_slice(&data), + russh::ChannelMsg::ExitStatus { exit_status: code } => exit_status = Some(code), + russh::ChannelMsg::Close => break, + _ => {} + } + } + + match exit_status.unwrap_or(0) { + 0 => Ok(()), + code => { + let stderr = String::from_utf8_lossy(&stderr).trim().to_string(); + let stdout = String::from_utf8_lossy(&stdout).trim().to_string(); + Err(anyhow!( + "remote command exited with {code}: {}", + if !stderr.is_empty() { stderr } else { stdout } + )) + } + } +} + +fn remote_parent(path: &str) -> String { + if path == "/" { + "/".to_string() + } else { + path.rsplit_once('/') + .map(|(parent, _)| { + if parent.is_empty() { + "/".to_string() + } else { + parent.to_string() + } + }) + .unwrap_or_else(|| "/".to_string()) + } +} + +fn shell_quote(value: &str) -> String { + format!("'{}'", value.replace('\'', "'\"'\"'")) +} + +async fn maybe_extract_archive(path: &Path) -> Result> { + let Some(file_name) = path.file_name().and_then(|name| name.to_str()).map(|name| name.to_string()) else { + return Ok(None); + }; + let is_archive = [".zip", ".tar", ".tar.gz", ".tgz"] + .iter() + .any(|suffix| file_name.ends_with(suffix)); + if !is_archive { + return Ok(None); + } + + let extract_root = path + .parent() + .unwrap_or_else(|| Path::new(".")) + .join(strip_archive_suffix(&file_name)); + let archive_path = path.to_path_buf(); + let target_dir = extract_root.clone(); + + tokio::task::spawn_blocking(move || -> Result<()> { + fs::create_dir_all(&target_dir) + .with_context(|| format!("create {}", target_dir.display()))?; + + if file_name.ends_with(".zip") { + let file = fs::File::open(&archive_path) + .with_context(|| format!("open {}", archive_path.display()))?; + let mut zip = ZipArchive::new(file).context("read zip archive")?; + for index in 0..zip.len() { + let mut entry = zip.by_index(index).context("read zip entry")?; + let Some(name) = entry.enclosed_name().map(|name| name.to_path_buf()) else { + continue; + }; + let output = target_dir.join(name); + if entry.is_dir() { + fs::create_dir_all(&output)?; + } else { + if let Some(parent) = output.parent() { + fs::create_dir_all(parent)?; + } + let mut output_file = fs::File::create(&output)?; + std::io::copy(&mut entry, &mut output_file)?; + } + } + } else if file_name.ends_with(".tar.gz") || file_name.ends_with(".tgz") { + let file = fs::File::open(&archive_path) + .with_context(|| format!("open {}", archive_path.display()))?; + let decoder = GzDecoder::new(file); + let mut archive = tar::Archive::new(decoder); + archive.unpack(&target_dir).context("unpack tar.gz archive")?; + } else if file_name.ends_with(".tar") { + let file = fs::File::open(&archive_path) + .with_context(|| format!("open {}", archive_path.display()))?; + let mut archive = tar::Archive::new(file); + archive.unpack(&target_dir).context("unpack tar archive")?; + } + + Ok(()) + }) + .await + .context("extract archive task join failure")??; + + Ok(Some(extract_root)) +} + +async fn extract_archive_to(path: &Path, target_dir: &Path) -> Result<()> { + let Some(file_name) = path.file_name().and_then(|name| name.to_str()).map(|name| name.to_string()) else { + return Ok(()); + }; + let archive_path = path.to_path_buf(); + let target_dir = target_dir.to_path_buf(); + + tokio::task::spawn_blocking(move || -> Result<()> { + fs::create_dir_all(&target_dir) + .with_context(|| format!("create {}", target_dir.display()))?; + + if file_name.ends_with(".zip") { + let file = fs::File::open(&archive_path) + .with_context(|| format!("open {}", archive_path.display()))?; + let mut zip = ZipArchive::new(file).context("read zip archive")?; + for index in 0..zip.len() { + let mut entry = zip.by_index(index).context("read zip entry")?; + let Some(name) = entry.enclosed_name().map(|name| name.to_path_buf()) else { + continue; + }; + let output = target_dir.join(name); + if entry.is_dir() { + fs::create_dir_all(&output)?; + } else { + if let Some(parent) = output.parent() { + fs::create_dir_all(parent)?; + } + let mut output_file = fs::File::create(&output)?; + std::io::copy(&mut entry, &mut output_file)?; + } + } + } else if file_name.ends_with(".tar.gz") || file_name.ends_with(".tgz") { + let file = fs::File::open(&archive_path) + .with_context(|| format!("open {}", archive_path.display()))?; + let decoder = GzDecoder::new(file); + let mut archive = tar::Archive::new(decoder); + archive.unpack(&target_dir).context("unpack tar.gz archive")?; + } else if file_name.ends_with(".tar") { + let file = fs::File::open(&archive_path) + .with_context(|| format!("open {}", archive_path.display()))?; + let mut archive = tar::Archive::new(file); + archive.unpack(&target_dir).context("unpack tar archive")?; + } + + Ok(()) + }) + .await + .context("extract archive task join failure")??; + + Ok(()) +} + +struct SftpClientHandler; + +#[async_trait] +impl Handler for SftpClientHandler { + type Error = anyhow::Error; + + async fn check_server_key( + &mut self, + _server_public_key: &russh::keys::ssh_key::PublicKey, + ) -> Result { + Ok(true) + } +} diff --git a/src/ssh_terminal.rs b/src/ssh_terminal.rs new file mode 100644 index 0000000..ab90eb5 --- /dev/null +++ b/src/ssh_terminal.rs @@ -0,0 +1,432 @@ +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; + +use anyhow::{anyhow, Context, Result}; +use async_trait::async_trait; +use directories::BaseDirs; +use russh::{ + client::{self, Handler}, + keys::{decode_secret_key, key::PrivateKeyWithHashAlg, load_secret_key, HashAlg, PrivateKey}, + ChannelMsg, Disconnect, +}; +use tokio::sync::mpsc; + +use crate::{ + config::{AuthMethod, Session}, + system::{remote_snapshot_from_kv, SystemSnapshot}, + terminal::{BackendCommand, BackendEvent, BackendTx}, +}; + +pub fn spawn_ssh_terminal( + runtime: &tokio::runtime::Handle, + tab_id: String, + session: Session, + cols: u16, + rows: u16, + events: std::sync::mpsc::Sender, +) -> BackendTx { + let (cmd_tx, cmd_rx) = mpsc::unbounded_channel::(); + let task_tab = tab_id.clone(); + runtime.spawn(async move { + if let Err(err) = run_ssh( + task_tab.clone(), + session, + cols, + rows, + cmd_rx, + events.clone(), + ) + .await + { + let _ = events.send(BackendEvent::Closed { + tab_id: task_tab, + reason: format!("{err:#}"), + }); + } + }); + BackendTx::Ssh(cmd_tx) +} + +pub async fn sample_remote_system(session: Session) -> Result { + let (events_tx, _events_rx) = std::sync::mpsc::channel(); + let handle = connect_and_authenticate("remote-metrics", &session, &events_tx).await?; + let mut channel = handle + .channel_open_session() + .await + .context("open metrics session")?; + channel + .exec(true, REMOTE_SYSTEM_PROBE) + .await + .context("exec remote metrics probe")?; + + let mut stdout = Vec::new(); + while let Some(msg) = channel.wait().await { + match msg { + ChannelMsg::Data { data } | ChannelMsg::ExtendedData { data, ext: _ } => { + stdout.extend_from_slice(&data); + } + ChannelMsg::Close => break, + _ => {} + } + } + + let _ = handle + .disconnect(Disconnect::ByApplication, "metrics done", "") + .await; + let output = String::from_utf8_lossy(&stdout); + remote_snapshot_from_kv(&output) +} + +async fn run_ssh( + tab_id: String, + session: Session, + cols: u16, + rows: u16, + mut commands: mpsc::UnboundedReceiver, + events: std::sync::mpsc::Sender, +) -> Result<()> { + let _ = events.send(BackendEvent::Status { + tab_id: tab_id.clone(), + text: format!( + "connecting {}@{}:{}...", + session.user, session.host, session.port + ), + }); + + let handle = connect_and_authenticate(&tab_id, &session, &events).await?; + + let mut channel = handle + .channel_open_session() + .await + .context("open session")?; + channel + .request_pty(true, "xterm-256color", cols.into(), rows.into(), 0, 0, &[]) + .await + .context("request pty")?; + channel.request_shell(true).await.context("request shell")?; + + let _ = events.send(BackendEvent::Status { + tab_id: tab_id.clone(), + text: format!("connected {}@{}", session.user, session.host), + }); + let _ = events.send(BackendEvent::Connected { + tab_id: tab_id.clone(), + }); + + loop { + tokio::select! { + command = commands.recv() => { + match command { + Some(BackendCommand::Input(bytes)) => { + if let Err(err) = channel.data(bytes.as_slice()).await { + let _ = events.send(BackendEvent::Closed { + tab_id: tab_id.clone(), + reason: format!("ssh write error: {err}"), + }); + break; + } + } + Some(BackendCommand::Resize { cols, rows }) => { + let _ = channel.window_change(cols.into(), rows.into(), 0, 0).await; + } + Some(BackendCommand::Close) | None => { + let _ = channel.eof().await; + break; + } + } + } + msg = channel.wait() => { + match msg { + Some(ChannelMsg::Data { data }) | Some(ChannelMsg::ExtendedData { data, ext: _ }) => { + let _ = events.send(BackendEvent::Output { + tab_id: tab_id.clone(), + bytes: data.to_vec(), + }); + } + Some(ChannelMsg::Close) | None => break, + _ => {} + } + } + } + } + + let _ = handle + .disconnect(Disconnect::ByApplication, "bye", "") + .await; + let _ = events.send(BackendEvent::Closed { + tab_id, + reason: "ssh session closed".into(), + }); + Ok(()) +} + +async fn connect_and_authenticate( + tab_id: &str, + session: &Session, + events: &std::sync::mpsc::Sender, +) -> Result> { + let config = Arc::new(client::Config { + inactivity_timeout: Some(std::time::Duration::from_secs(600)), + ..Default::default() + }); + let addr = format!("{}:{}", session.host, session.port); + let _ = events.send(BackendEvent::Status { + tab_id: tab_id.to_string(), + text: format!("opening tcp connection to {addr}"), + }); + let mut handle = client::connect(config, addr.as_str(), ClientHandler) + .await + .with_context(|| format!("connect {addr} failed"))?; + + let authed = match session.auth { + AuthMethod::Password => { + let _ = events.send(BackendEvent::Status { + tab_id: tab_id.to_string(), + text: format!( + "connected to {addr}, sending password authentication for {}", + session.user + ), + }); + handle + .authenticate_password(&session.user, &session.password) + .await + .context("password authentication failed")? + } + AuthMethod::Key => { + let source = key_source_label(session); + let _ = events.send(BackendEvent::Status { + tab_id: tab_id.to_string(), + text: format!("connected to {addr}, loading private key from {source}"), + }); + let keypair = load_session_private_key(session)?; + let algorithm = format!("{:?}", keypair.algorithm()); + let _ = events.send(BackendEvent::Status { + tab_id: tab_id.to_string(), + text: format!("private key loaded from {source}, algorithm {algorithm}, sending public key authentication for {}", session.user), + }); + let key = private_key_with_alg(keypair).context("invalid private key")?; + handle + .authenticate_publickey(&session.user, key) + .await + .with_context(|| { + format!( + "public key authentication failed for {}@{}:{} using {} ({})", + session.user, session.host, session.port, source, algorithm + ) + })? + } + }; + + if !authed { + let _ = handle + .disconnect(Disconnect::ByApplication, "auth failed", "") + .await; + return Err(anyhow!( + "{}", + match session.auth { + AuthMethod::Password => format!( + "authentication failed: server rejected password authentication for {}@{}:{}", + session.user, session.host, session.port + ), + AuthMethod::Key => format!( + "authentication failed: server rejected public key authentication for {}@{}:{} using {}", + session.user, + session.host, + session.port, + key_source_label(session) + ), + } + )); + } + + let _ = events.send(BackendEvent::Status { + tab_id: tab_id.to_string(), + text: format!( + "authentication accepted, opening shell for {}@{}", + session.user, session.host + ), + }); + + Ok(handle) +} + +fn load_session_private_key(session: &Session) -> Result { + let inline_key = normalize_inline_private_key(&session.private_key_inline); + let key_path = expand_key_path(session.private_key_path.trim()); + let has_inline = !inline_key.is_empty(); + let has_path = key_path.is_some(); + + if !has_inline && !has_path { + return Err(anyhow!("private key content or path is required")); + } + + let mut errors = Vec::new(); + + if has_inline { + match decode_secret_key(&inline_key, None) { + Ok(key) => return Ok(key), + Err(err) => errors.push(format!("decode private key content: {err}")), + } + } + + if let Some(path) = key_path { + match load_secret_key(path.as_path(), None) { + Ok(key) => return Ok(key), + Err(err) => errors.push(format!("load key {}: {err}", path.display())), + } + } + + Err(anyhow!(errors.join("; "))) +} + +fn private_key_with_alg(keypair: PrivateKey) -> Result { + let hash_alg = if keypair.algorithm().is_rsa() { + Some(HashAlg::Sha512) + } else { + None + }; + Ok( + PrivateKeyWithHashAlg::new(Arc::new(keypair.clone()), hash_alg) + .or_else(|_| PrivateKeyWithHashAlg::new(Arc::new(keypair), Some(HashAlg::Sha256)))?, + ) +} + +fn normalize_inline_private_key(value: &str) -> String { + let mut normalized = value + .trim() + .replace("\\r\\n", "\n") + .replace("\\n", "\n") + .replace("\r\n", "\n"); + if !normalized.ends_with('\n') { + normalized.push('\n'); + } + normalized +} + +fn expand_key_path(value: &str) -> Option { + if value.is_empty() { + return None; + } + if value == "~" { + return BaseDirs::new().map(|dirs| dirs.home_dir().to_path_buf()); + } + if let Some(rest) = value.strip_prefix("~/") { + return BaseDirs::new().map(|dirs| dirs.home_dir().join(rest)); + } + Some(Path::new(value).to_path_buf()) +} + +fn key_source_label(session: &Session) -> String { + let path = session.private_key_path.trim(); + let has_inline = !session.private_key_inline.trim().is_empty(); + match (!path.is_empty(), has_inline) { + (true, true) => format!("inline key or {}", path), + (true, false) => path.to_string(), + (false, true) => "inline key text".to_string(), + (false, false) => "unknown key source".to_string(), + } +} + +const REMOTE_SYSTEM_PROBE: &str = r#"sh -lc ' +os=$(uname -s 2>/dev/null || echo unknown) + +if [ "$os" = "Linux" ] && [ -r /proc/stat ]; then + cpu_stat() { awk '"'"'/^cpu / { print ($2+$3+$4+$5+$6+$7+$8), $5 }'"'"' /proc/stat 2>/dev/null; } + net_stat() { awk -F"[: ]+" '"'"'/:/ && $1!="Inter" && $1!="face" { rx += $3; tx += $11 } END { print rx+0, tx+0 }'"'"' /proc/net/dev 2>/dev/null; } + + read cpu_total_1 cpu_idle_1 </dev/null) + mem_available=$(awk '"'"'/^MemAvailable:/ {print $2 * 1024}'"'"' /proc/meminfo 2>/dev/null) + swap_total=$(awk '"'"'/^SwapTotal:/ {print $2 * 1024}'"'"' /proc/meminfo 2>/dev/null) + swap_free=$(awk '"'"'/^SwapFree:/ {print $2 * 1024}'"'"' /proc/meminfo 2>/dev/null) + + echo "CPU_PERCENT=${cpu_percent:-0.00}" + echo "MEM_TOTAL=${mem_total:-0}" + echo "MEM_USED=$(( ${mem_total:-0} - ${mem_available:-0} ))" + echo "SWAP_TOTAL=${swap_total:-0}" + echo "SWAP_USED=$(( ${swap_total:-0} - ${swap_free:-0} ))" + echo "NET_RX=$(( ${net_rx_2:-0} - ${net_rx_1:-0} ))" + echo "NET_TX=$(( ${net_tx_2:-0} - ${net_tx_1:-0} ))" + df -kP 2>/dev/null | awk "NR > 1 { printf \"DISK=%s\t%s\t%s\n\", \$6, \$4 * 1024, \$2 * 1024 }" | head -n 4 + exit 0 +fi + +if [ "$os" = "Darwin" ]; then + net_stat() { netstat -ibn 2>/dev/null | awk '"'"'NR > 1 && $7 ~ /^[0-9]+$/ && $10 ~ /^[0-9]+$/ { rx += $7; tx += $10 } END { print rx+0, tx+0 }'"'"'; } + + read net_rx_1 net_tx_1 </dev/null | awk -F"[:,% ]+" '"'"'/CPU usage:/ { user=$3; sys=$5 } END { if (user == "" && sys == "") print "0.00"; else printf "%.2f", user + sys }'"'"') + mem_total=$(sysctl -n hw.memsize 2>/dev/null || echo 0) + pagesize=$(sysctl -n hw.pagesize 2>/dev/null || echo 4096) + vm_output=$(vm_stat 2>/dev/null) + pages_active=$(printf "%s\n" "$vm_output" | awk '"'"'/Pages active/ { gsub("\\.","",$3); print $3+0 }'"'"') + pages_wired=$(printf "%s\n" "$vm_output" | awk '"'"'/Pages wired down/ { gsub("\\.","",$4); print $4+0 }'"'"') + pages_compressed=$(printf "%s\n" "$vm_output" | awk '"'"'/Pages occupied by compressor/ { gsub("\\.","",$5); print $5+0 }'"'"') + pages_speculative=$(printf "%s\n" "$vm_output" | awk '"'"'/Pages speculative/ { gsub("\\.","",$3); print $3+0 }'"'"') + mem_used=$(( (${pages_active:-0} + ${pages_wired:-0} + ${pages_compressed:-0} + ${pages_speculative:-0}) * ${pagesize:-4096} )) + swap_line=$(sysctl vm.swapusage 2>/dev/null || true) + swap_used=$(printf "%s\n" "$swap_line" | awk -F"[= ,]+" '"'"' + function mult(unit) { return unit=="K"?1024:(unit=="M"?1048576:(unit=="G"?1073741824:(unit=="T"?1099511627776:1))) } + /used/ { value=$4; unit=substr(value, length(value), 1); sub(/[A-Za-z]+$/, "", value); printf "%.0f", value * mult(unit) }'"'"') + swap_total=$(printf "%s\n" "$swap_line" | awk -F"[= ,]+" '"'"' + function mult(unit) { return unit=="K"?1024:(unit=="M"?1048576:(unit=="G"?1073741824:(unit=="T"?1099511627776:1))) } + /used/ && /free/ { used=$4; free=$8; unit1=substr(used, length(used), 1); unit2=substr(free, length(free), 1); sub(/[A-Za-z]+$/, "", used); sub(/[A-Za-z]+$/, "", free); printf "%.0f", (used * mult(unit1)) + (free * mult(unit2)) }'"'"') + + echo "CPU_PERCENT=${cpu_percent:-0.00}" + echo "MEM_TOTAL=${mem_total:-0}" + echo "MEM_USED=${mem_used:-0}" + echo "SWAP_TOTAL=${swap_total:-0}" + echo "SWAP_USED=${swap_used:-0}" + echo "NET_RX=$(( ${net_rx_2:-0} - ${net_rx_1:-0} ))" + echo "NET_TX=$(( ${net_tx_2:-0} - ${net_tx_1:-0} ))" + df -kP 2>/dev/null | awk "NR > 1 { printf \"DISK=%s\t%s\t%s\n\", \$6, \$4 * 1024, \$2 * 1024 }" | head -n 4 + exit 0 +fi + +echo "CPU_PERCENT=0.00" +echo "MEM_TOTAL=0" +echo "MEM_USED=0" +echo "SWAP_TOTAL=0" +echo "SWAP_USED=0" +echo "NET_RX=0" +echo "NET_TX=0" +'"#; + +struct ClientHandler; + +#[async_trait] +impl Handler for ClientHandler { + type Error = anyhow::Error; + + async fn check_server_key( + &mut self, + _server_public_key: &russh::keys::ssh_key::PublicKey, + ) -> Result { + Ok(true) + } +} diff --git a/src/system.rs b/src/system.rs new file mode 100644 index 0000000..93bb668 --- /dev/null +++ b/src/system.rs @@ -0,0 +1,197 @@ +use std::{collections::BTreeMap, time::{Duration, Instant}}; + +use anyhow::{anyhow, Result}; +use sysinfo::{Disks, Networks, System}; + +#[derive(Debug, Clone, Default)] +pub struct DiskSample { + pub mount: String, + pub available_bytes: u64, + pub total_bytes: u64, +} + +#[derive(Debug, Clone, Default)] +pub struct SystemSnapshot { + pub cpu_percent: f32, + pub mem_percent: f32, + pub swap_percent: f32, + pub mem_detail: String, + pub swap_detail: String, + pub net_rx: String, + pub net_tx: String, + pub disks: Vec, +} + +pub struct SystemSampler { + sys: System, + nets: Networks, + disks: Disks, + last_rx_total: u64, + last_tx_total: u64, + last_instant: Instant, +} + +impl SystemSampler { + pub fn new() -> Self { + let mut sys = System::new_all(); + sys.refresh_all(); + let nets = Networks::new_with_refreshed_list(); + let disks = Disks::new_with_refreshed_list(); + let last_rx_total = nets.iter().map(|(_, d)| d.total_received()).sum(); + let last_tx_total = nets.iter().map(|(_, d)| d.total_transmitted()).sum(); + + Self { + sys, + nets, + disks, + last_rx_total, + last_tx_total, + last_instant: Instant::now(), + } + } + + pub fn interval() -> Duration { + Duration::from_millis(1000) + } + + pub fn sample(&mut self) -> SystemSnapshot { + self.sys.refresh_cpu_usage(); + self.sys.refresh_memory(); + self.nets.refresh(true); + self.disks.refresh(true); + + let cpu_percent = self.sys.global_cpu_usage() / 100.0; + let mem_total = self.sys.total_memory(); + let mem_used = self.sys.used_memory(); + let swap_total = self.sys.total_swap(); + let swap_used = self.sys.used_swap(); + + let rx_total: u64 = self.nets.iter().map(|(_, d)| d.total_received()).sum(); + let tx_total: u64 = self.nets.iter().map(|(_, d)| d.total_transmitted()).sum(); + let now = Instant::now(); + let elapsed = now.duration_since(self.last_instant).as_secs_f64().max(0.001); + let rx_rate = (rx_total.saturating_sub(self.last_rx_total) as f64 / elapsed) as u64; + let tx_rate = (tx_total.saturating_sub(self.last_tx_total) as f64 / elapsed) as u64; + self.last_rx_total = rx_total; + self.last_tx_total = tx_total; + self.last_instant = now; + + let disks = self + .disks + .iter() + .filter(|disk| disk.total_space() > 0) + .take(4) + .map(|disk| DiskSample { + mount: disk.mount_point().to_string_lossy().to_string(), + available_bytes: disk.available_space(), + total_bytes: disk.total_space(), + }) + .collect(); + + SystemSnapshot { + cpu_percent, + mem_percent: ratio(mem_used, mem_total), + swap_percent: ratio(swap_used, swap_total), + mem_detail: format!( + "{}/{}", + format_bytes(mem_used), + format_bytes(mem_total) + ), + swap_detail: format!( + "{}/{}", + format_bytes(swap_used), + format_bytes(swap_total) + ), + net_rx: format!("{}/s", format_bytes(rx_rate)), + net_tx: format!("{}/s", format_bytes(tx_rate)), + disks, + } + } +} + +fn ratio(used: u64, total: u64) -> f32 { + if total == 0 { + 0.0 + } else { + (used as f32 / total as f32).clamp(0.0, 1.0) + } +} + +pub fn format_bytes(bytes: u64) -> String { + const UNITS: [&str; 5] = ["B", "KB", "MB", "GB", "TB"]; + let mut value = bytes as f64; + let mut unit = 0; + while value >= 1024.0 && unit < UNITS.len() - 1 { + value /= 1024.0; + unit += 1; + } + if unit == 0 { + format!("{bytes} {}", UNITS[unit]) + } else { + format!("{value:.1} {}", UNITS[unit]) + } +} + +pub fn remote_snapshot_from_kv(raw: &str) -> Result { + let mut kv = BTreeMap::new(); + let mut disks = Vec::new(); + + for line in raw.lines().map(str::trim).filter(|line| !line.is_empty()) { + if let Some(rest) = line.strip_prefix("DISK=") { + let mut parts = rest.split('\t'); + let mount = parts.next().unwrap_or_default().to_string(); + let available_bytes = parts + .next() + .unwrap_or("0") + .parse::() + .unwrap_or_default(); + let total_bytes = parts + .next() + .unwrap_or("0") + .parse::() + .unwrap_or_default(); + disks.push(DiskSample { + mount, + available_bytes, + total_bytes, + }); + continue; + } + + let Some((key, value)) = line.split_once('=') else { + continue; + }; + kv.insert(key.to_string(), value.to_string()); + } + + let cpu_percent = kv + .get("CPU_PERCENT") + .ok_or_else(|| anyhow!("missing CPU_PERCENT"))? + .parse::() + .unwrap_or_default() + / 100.0; + + let mem_used = parse_u64(&kv, "MEM_USED"); + let mem_total = parse_u64(&kv, "MEM_TOTAL"); + let swap_used = parse_u64(&kv, "SWAP_USED"); + let swap_total = parse_u64(&kv, "SWAP_TOTAL"); + let rx_rate = parse_u64(&kv, "NET_RX"); + let tx_rate = parse_u64(&kv, "NET_TX"); + + Ok(SystemSnapshot { + cpu_percent: cpu_percent.clamp(0.0, 1.0), + mem_percent: ratio(mem_used, mem_total), + swap_percent: ratio(swap_used, swap_total), + mem_detail: format!("{}/{}", format_bytes(mem_used), format_bytes(mem_total)), + swap_detail: format!("{}/{}", format_bytes(swap_used), format_bytes(swap_total)), + net_rx: format!("{}/s", format_bytes(rx_rate)), + net_tx: format!("{}/s", format_bytes(tx_rate)), + disks, + }) +} + +fn parse_u64(kv: &BTreeMap, key: &str) -> u64 { + kv.get(key) + .and_then(|value| value.parse::().ok()) + .unwrap_or_default() +} diff --git a/src/terminal.rs b/src/terminal.rs new file mode 100644 index 0000000..26235fb --- /dev/null +++ b/src/terminal.rs @@ -0,0 +1,557 @@ +use std::sync::mpsc::Sender; + +use alacritty_terminal::{ + event::{Event, EventListener}, + grid::{Dimensions, Scroll}, + index::{Column, Point, Side}, + selection::{Selection, SelectionRange, SelectionType}, + term::{ + cell::Cell, + point_to_viewport, viewport_to_point, Config, Term, TermMode, + }, + vte::ansi::{CursorShape, Processor}, +}; +use gpui::Keystroke; + +use crate::config::Session; +use crate::sftp::{PreviewData, RemoteEntry}; +use crate::system::SystemSnapshot; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TabKind { + Local, + Ssh, +} + +#[derive(Debug)] +pub enum BackendCommand { + Input(Vec), + Resize { cols: u16, rows: u16 }, + Close, +} + +#[derive(Debug, Clone)] +pub enum BackendEvent { + Output { tab_id: String, bytes: Vec }, + Status { tab_id: String, text: String }, + Connected { tab_id: String }, + SftpEntries { tab_id: String, path: String, entries: Vec }, + SftpPreview { tab_id: String, preview: PreviewData }, + SftpStatus { tab_id: String, text: String }, + RemoteSystem { tab_id: String, snapshot: SystemSnapshot }, + RemoteSystemUnavailable { tab_id: String, reason: String }, + Closed { tab_id: String, reason: String }, +} + +#[derive(Clone)] +pub enum BackendTx { + Local(Sender), + Ssh(tokio::sync::mpsc::UnboundedSender), +} + +impl BackendTx { + pub fn send(&self, command: BackendCommand) { + match self { + Self::Local(tx) => { + let _ = tx.send(command); + } + Self::Ssh(tx) => { + let _ = tx.send(command); + } + } + } +} + +pub struct TerminalTab { + pub id: String, + pub title: String, + pub kind: TabKind, + pub status: String, + pub connected: bool, + pub session: Option, + pub sftp: Option, + processor: Processor, + term: Term, + cols: u16, + rows: u16, + pub backend: BackendTx, +} + +#[derive(Clone, Copy)] +pub struct CursorState { + pub row: usize, + pub col: usize, + pub shape: CursorShape, +} + +#[derive(Clone)] +pub struct RenderCell { + pub row: i32, + pub col: i32, + pub cell: Cell, +} + +#[derive(Clone)] +pub struct RenderSnapshot { + pub cells: Vec, + pub cursor: Option, + pub selection: Option, + pub display_offset: usize, + pub history_size: usize, + pub rows: usize, + pub cols: usize, +} + +#[derive(Clone, Copy)] +pub struct ViewportSelection { + pub start_row: usize, + pub start_col: usize, + pub end_row: usize, + pub end_col: usize, + pub is_block: bool, +} + +#[derive(Clone, Default)] +pub struct SftpUiState { + pub current_path: String, + pub status: String, + pub entries: Vec, + pub selected_path: Option, + pub preview: Option, +} + +impl TerminalTab { + pub fn new_local(id: String, title: String, backend: BackendTx) -> Self { + Self::new(id, title, TabKind::Local, "local shell".into(), backend) + } + + pub fn new_ssh(id: String, session: &Session, backend: BackendTx) -> Self { + let mut tab = Self::new( + id, + session.name.clone(), + TabKind::Ssh, + format!("connecting {}@{}:{}", session.user, session.host, session.port), + backend, + ); + tab.session = Some(session.clone()); + tab.connected = false; + tab.sftp = Some(SftpUiState { + current_path: "/".into(), + status: "sftp connecting...".into(), + entries: Vec::new(), + selected_path: None, + preview: None, + }); + tab + } + + fn new(id: String, title: String, kind: TabKind, status: String, backend: BackendTx) -> Self { + Self { + id, + title, + kind, + status, + connected: matches!(kind, TabKind::Local), + session: None, + sftp: None, + processor: Processor::new(), + term: new_term(100, 30, backend.clone()), + cols: 100, + rows: 30, + backend, + } + } + + pub fn feed(&mut self, bytes: &[u8]) { + self.processor.advance(&mut self.term, bytes); + } + + pub fn resize(&mut self, cols: u16, rows: u16) { + self.cols = cols.max(1); + self.rows = rows.max(1); + self.term.resize(TerminalSize::new(self.cols, self.rows)); + self.backend.send(BackendCommand::Resize { cols, rows }); + } + + pub fn cursor_state(&self) -> Option { + let content = self.term.renderable_content(); + if matches!(content.cursor.shape, CursorShape::Hidden) || content.display_offset > 0 { + return None; + } + let row = content.cursor.point.line.0; + if row < 0 { + return None; + } + let row = row as usize; + if row >= self.rows as usize { + return None; + } + + Some(CursorState { + row, + col: content.cursor.point.column.0, + shape: content.cursor.shape, + }) + } + + pub fn app_cursor_mode(&self) -> bool { + self.term.mode().contains(TermMode::APP_CURSOR) + } + + pub fn render_snapshot(&self) -> RenderSnapshot { + let rows = self.rows; + let cols = self.cols; + let content = self.term.renderable_content(); + let display_offset = content.display_offset as i32; + let mut cells = Vec::with_capacity((rows as usize) * (cols as usize)); + + for indexed in content.display_iter { + let line = indexed.point.line.0; + let row = line + display_offset; + if row < 0 { + continue; + } + if row >= rows as i32 { + continue; + } + + let col = indexed.point.column.0 as i32; + if col >= cols as i32 { + continue; + } + + cells.push(RenderCell { + row, + col, + cell: indexed.cell.clone(), + }); + } + + RenderSnapshot { + cells, + cursor: self.cursor_state(), + selection: viewport_selection_from_range(content.display_offset, &content.selection), + display_offset: content.display_offset, + history_size: self.term.grid().history_size(), + rows: self.rows as usize, + cols: self.cols as usize, + } + } + + pub fn scroll_history(&mut self, delta: i32) { + if delta != 0 { + self.term.scroll_display(Scroll::Delta(delta)); + } + } + + pub fn scroll_up_by(&mut self, lines: usize) { + if lines != 0 { + self.term.scroll_display(Scroll::Delta(lines as i32)); + } + } + + pub fn scroll_down_by(&mut self, lines: usize) { + if lines != 0 { + self.term.scroll_display(Scroll::Delta(-(lines as i32))); + } + } + + pub fn scroll_to_bottom(&mut self) { + self.term.scroll_display(Scroll::Bottom); + } + + pub fn has_selection(&self) -> bool { + self.term.selection_to_string().is_some_and(|text| !text.is_empty()) + } + + pub fn clear_selection(&mut self) { + self.term.selection = None; + } + + pub fn selection_text(&self) -> Option { + self.term.selection_to_string().filter(|text| !text.is_empty()) + } + + pub fn begin_selection( + &mut self, + row: usize, + col: usize, + side: Side, + selection_type: SelectionType, + ) { + let point = viewport_to_point( + self.term.grid().display_offset(), + Point::new(row, Column(col)), + ); + self.term.selection = Some(Selection::new(selection_type, point, side)); + } + + pub fn update_selection(&mut self, row: usize, col: usize, side: Side) { + let point = viewport_to_point( + self.term.grid().display_offset(), + Point::new(row, Column(col)), + ); + if let Some(selection) = self.term.selection.as_mut() { + selection.update(point, side); + } + } + + pub fn paste_text(&mut self, text: &str) { + let paste_text = text.replace('\x1b', "").replace("\r\n", "\r").replace('\n', "\r"); + + self.backend + .send(BackendCommand::Input(paste_text.into_bytes())); + } + +} + +fn viewport_selection_from_range( + display_offset: usize, + selection: &Option, +) -> Option { + let SelectionRange { start, end, is_block } = selection.as_ref().copied()?; + let start = point_to_viewport(display_offset, start)?; + let end = point_to_viewport(display_offset, end)?; + + Some(ViewportSelection { + start_row: start.line, + start_col: start.column.0, + end_row: end.line, + end_col: end.column.0, + is_block, + }) +} + +#[derive(Clone)] +struct TerminalListener { + backend: BackendTx, +} + +impl EventListener for TerminalListener { + fn send_event(&self, event: Event) { + match event { + Event::PtyWrite(output) => self.backend.send(BackendCommand::Input(output.into_bytes())), + Event::TextAreaSizeRequest(format) => { + let size = alacritty_terminal::event::WindowSize { + num_lines: 30, + num_cols: 100, + cell_width: 8, + cell_height: 16, + }; + self.backend + .send(BackendCommand::Input(format(size).into_bytes())); + } + _ => {} + } + } +} + +fn new_term(cols: u16, rows: u16, backend: BackendTx) -> Term { + Term::new( + Config { + scrolling_history: 2000, + ..Config::default() + }, + &TerminalSize::new(cols, rows), + TerminalListener { backend }, + ) +} + +struct TerminalSize { + cols: usize, + rows: usize, +} + +impl TerminalSize { + fn new(cols: u16, rows: u16) -> Self { + Self { + cols: cols.max(1) as usize, + rows: rows.max(1) as usize, + } + } +} + +impl Dimensions for TerminalSize { + fn total_lines(&self) -> usize { + self.rows + } + + fn screen_lines(&self) -> usize { + self.rows + } + + fn columns(&self) -> usize { + self.cols + } +} + +pub fn encode_key(keystroke: &Keystroke, app_cursor_mode: bool, option_as_meta: bool) -> Option> { + zed_like_to_esc_str(keystroke, app_cursor_mode, option_as_meta) + .map(|text| text.into_owned().into_bytes()) +} + +#[derive(Debug, PartialEq, Eq)] +enum TerminalModifiers { + None, + Alt, + Ctrl, + Shift, + CtrlShift, + Other, +} + +impl TerminalModifiers { + fn new(ks: &Keystroke) -> Self { + match ( + ks.modifiers.alt, + ks.modifiers.control, + ks.modifiers.shift, + ks.modifiers.platform, + ) { + (false, false, false, false) => Self::None, + (true, false, false, false) => Self::Alt, + (false, true, false, false) => Self::Ctrl, + (false, false, true, false) => Self::Shift, + (false, true, true, false) => Self::CtrlShift, + _ => Self::Other, + } + } + + fn any(&self) -> bool { + !matches!(self, Self::None) + } +} + +fn zed_like_to_esc_str( + keystroke: &Keystroke, + app_cursor_mode: bool, + option_as_meta: bool, +) -> Option> { + let modifiers = TerminalModifiers::new(keystroke); + let key = keystroke.key.to_ascii_lowercase(); + + let manual_esc_str = match (key.as_str(), &modifiers) { + ("tab", TerminalModifiers::None) => Some("\x09"), + ("tab", TerminalModifiers::Shift) => Some("\x1b[Z"), + ("escape", TerminalModifiers::None) => Some("\x1b"), + ("enter", TerminalModifiers::None) => Some("\x0d"), + ("enter", TerminalModifiers::Shift) => Some("\x0a"), + ("enter", TerminalModifiers::Alt) => Some("\x1b\x0d"), + ("backspace", TerminalModifiers::None) => Some("\x7f"), + ("backspace", TerminalModifiers::Ctrl) => Some("\x08"), + ("backspace", TerminalModifiers::Alt) => Some("\x1b\x7f"), + ("backspace", TerminalModifiers::Shift) => Some("\x7f"), + ("space", TerminalModifiers::Ctrl) => Some("\x00"), + ("home", TerminalModifiers::None) if app_cursor_mode => Some("\x1bOH"), + ("home", TerminalModifiers::None) if !app_cursor_mode => Some("\x1b[H"), + ("end", TerminalModifiers::None) if app_cursor_mode => Some("\x1bOF"), + ("end", TerminalModifiers::None) if !app_cursor_mode => Some("\x1b[F"), + ("up", TerminalModifiers::None) if app_cursor_mode => Some("\x1bOA"), + ("up", TerminalModifiers::None) if !app_cursor_mode => Some("\x1b[A"), + ("down", TerminalModifiers::None) if app_cursor_mode => Some("\x1bOB"), + ("down", TerminalModifiers::None) if !app_cursor_mode => Some("\x1b[B"), + ("right", TerminalModifiers::None) if app_cursor_mode => Some("\x1bOC"), + ("right", TerminalModifiers::None) if !app_cursor_mode => Some("\x1b[C"), + ("left", TerminalModifiers::None) if app_cursor_mode => Some("\x1bOD"), + ("left", TerminalModifiers::None) if !app_cursor_mode => Some("\x1b[D"), + ("insert", TerminalModifiers::None) => Some("\x1b[2~"), + ("delete", TerminalModifiers::None) => Some("\x1b[3~"), + ("pageup", TerminalModifiers::None) => Some("\x1b[5~"), + ("pagedown", TerminalModifiers::None) => Some("\x1b[6~"), + ("a", TerminalModifiers::Ctrl) | ("A", TerminalModifiers::CtrlShift) => Some("\x01"), + ("b", TerminalModifiers::Ctrl) | ("B", TerminalModifiers::CtrlShift) => Some("\x02"), + ("c", TerminalModifiers::Ctrl) | ("C", TerminalModifiers::CtrlShift) => Some("\x03"), + ("d", TerminalModifiers::Ctrl) | ("D", TerminalModifiers::CtrlShift) => Some("\x04"), + ("e", TerminalModifiers::Ctrl) | ("E", TerminalModifiers::CtrlShift) => Some("\x05"), + ("f", TerminalModifiers::Ctrl) | ("F", TerminalModifiers::CtrlShift) => Some("\x06"), + ("g", TerminalModifiers::Ctrl) | ("G", TerminalModifiers::CtrlShift) => Some("\x07"), + ("h", TerminalModifiers::Ctrl) | ("H", TerminalModifiers::CtrlShift) => Some("\x08"), + ("i", TerminalModifiers::Ctrl) | ("I", TerminalModifiers::CtrlShift) => Some("\x09"), + ("j", TerminalModifiers::Ctrl) | ("J", TerminalModifiers::CtrlShift) => Some("\x0a"), + ("k", TerminalModifiers::Ctrl) | ("K", TerminalModifiers::CtrlShift) => Some("\x0b"), + ("l", TerminalModifiers::Ctrl) | ("L", TerminalModifiers::CtrlShift) => Some("\x0c"), + ("m", TerminalModifiers::Ctrl) | ("M", TerminalModifiers::CtrlShift) => Some("\x0d"), + ("n", TerminalModifiers::Ctrl) | ("N", TerminalModifiers::CtrlShift) => Some("\x0e"), + ("o", TerminalModifiers::Ctrl) | ("O", TerminalModifiers::CtrlShift) => Some("\x0f"), + ("p", TerminalModifiers::Ctrl) | ("P", TerminalModifiers::CtrlShift) => Some("\x10"), + ("q", TerminalModifiers::Ctrl) | ("Q", TerminalModifiers::CtrlShift) => Some("\x11"), + ("r", TerminalModifiers::Ctrl) | ("R", TerminalModifiers::CtrlShift) => Some("\x12"), + ("s", TerminalModifiers::Ctrl) | ("S", TerminalModifiers::CtrlShift) => Some("\x13"), + ("t", TerminalModifiers::Ctrl) | ("T", TerminalModifiers::CtrlShift) => Some("\x14"), + ("u", TerminalModifiers::Ctrl) | ("U", TerminalModifiers::CtrlShift) => Some("\x15"), + ("v", TerminalModifiers::Ctrl) | ("V", TerminalModifiers::CtrlShift) => Some("\x16"), + ("w", TerminalModifiers::Ctrl) | ("W", TerminalModifiers::CtrlShift) => Some("\x17"), + ("x", TerminalModifiers::Ctrl) | ("X", TerminalModifiers::CtrlShift) => Some("\x18"), + ("y", TerminalModifiers::Ctrl) | ("Y", TerminalModifiers::CtrlShift) => Some("\x19"), + ("z", TerminalModifiers::Ctrl) | ("Z", TerminalModifiers::CtrlShift) => Some("\x1a"), + ("@", TerminalModifiers::Ctrl) => Some("\x00"), + ("[", TerminalModifiers::Ctrl) => Some("\x1b"), + ("\\", TerminalModifiers::Ctrl) => Some("\x1c"), + ("]", TerminalModifiers::Ctrl) => Some("\x1d"), + ("^", TerminalModifiers::Ctrl) => Some("\x1e"), + ("_", TerminalModifiers::Ctrl) => Some("\x1f"), + ("?", TerminalModifiers::Ctrl) => Some("\x7f"), + _ => None, + }; + if let Some(esc) = manual_esc_str { + return Some(esc.into()); + } + + if modifiers.any() { + let modifier_code = modifier_code(keystroke); + let modified = match key.as_str() { + "up" => Some(format!("\x1b[1;{}A", modifier_code)), + "down" => Some(format!("\x1b[1;{}B", modifier_code)), + "right" => Some(format!("\x1b[1;{}C", modifier_code)), + "left" => Some(format!("\x1b[1;{}D", modifier_code)), + "insert" => Some(format!("\x1b[2;{}~", modifier_code)), + "pageup" => Some(format!("\x1b[5;{}~", modifier_code)), + "pagedown" => Some(format!("\x1b[6;{}~", modifier_code)), + "end" => Some(format!("\x1b[1;{}F", modifier_code)), + "home" => Some(format!("\x1b[1;{}H", modifier_code)), + _ => None, + }; + if let Some(esc) = modified { + return Some(esc.into()); + } + } + + if !cfg!(target_os = "macos") || option_as_meta { + let is_alt_lowercase_ascii = + modifiers == TerminalModifiers::Alt && keystroke.key.is_ascii(); + let is_alt_uppercase_ascii = + keystroke.modifiers.alt && keystroke.modifiers.shift && keystroke.key.is_ascii(); + if is_alt_lowercase_ascii || is_alt_uppercase_ascii { + let key = if is_alt_uppercase_ascii { + keystroke.key.to_ascii_uppercase() + } else { + keystroke.key.clone() + }; + return Some(format!("\x1b{}", key).into()); + } + } + + if let Some(text) = &keystroke.key_char { + return Some(text.clone().into()); + } + + if keystroke.key.len() == 1 { + return Some(keystroke.key.clone().into()); + } + + None +} + +fn modifier_code(keystroke: &Keystroke) -> u32 { + let mut modifier_code = 0; + if keystroke.modifiers.shift { + modifier_code |= 1; + } + if keystroke.modifiers.alt { + modifier_code |= 1 << 1; + } + if keystroke.modifiers.control { + modifier_code |= 1 << 2; + } + modifier_code + 1 +} diff --git a/src/terminal_element.rs b/src/terminal_element.rs new file mode 100644 index 0000000..66e4a7d --- /dev/null +++ b/src/terminal_element.rs @@ -0,0 +1,689 @@ +use alacritty_terminal::{ + term::cell::Flags, + vte::ansi::{Color as AnsiColor, CursorShape, NamedColor}, +}; +use gpui::{ + App, Bounds, Element, ElementId, Entity, FocusHandle, Font, FontStyle, FontWeight, GlobalElementId, Hsla, InputHandler, IntoElement, + LayoutId, Pixels, Point, Rgba, StrikethroughStyle, TextRun, TextStyle, UTF16Selection, UnderlineStyle, Window, fill, point, px, + relative, rgb, +}; +use gpui_component::ActiveTheme as _; + +use crate::Ashell; +use crate::terminal::{RenderSnapshot, ViewportSelection}; + +#[derive(Clone, Copy)] +struct TerminalMetrics { + cell_width: Pixels, + line_height: Pixels, +} + +#[derive(Clone)] +struct LayoutRect { + row: i32, + col: i32, + cells: usize, + color: Hsla, +} + +impl LayoutRect { + fn paint(&self, origin: Point, metrics: TerminalMetrics, window: &mut Window) { + let position = point( + origin.x + metrics.cell_width * self.col as f32, + origin.y + metrics.line_height * self.row as f32, + ); + let size = gpui::size(metrics.cell_width * self.cells as f32, metrics.line_height); + window.paint_quad(fill(Bounds::new(position, size), self.color)); + } +} + +#[derive(Clone)] +struct BatchedTextRun { + row: i32, + col: i32, + cell_count: usize, + text: String, + style: TextRun, + font_size: Pixels, +} + +impl BatchedTextRun { + fn new(row: i32, col: i32, ch: char, style: TextRun, font_size: Pixels) -> Self { + Self { + row, + col, + cell_count: 1, + text: ch.to_string(), + style, + font_size, + } + } + + fn can_append(&self, other: &TextRun, row: i32, col: i32) -> bool { + self.row == row + && self.col + self.cell_count as i32 == col + && self.style.font == other.font + && self.style.color == other.color + && self.style.background_color == other.background_color + && self.style.underline == other.underline + && self.style.strikethrough == other.strikethrough + } + + fn append(&mut self, ch: char, zerowidth: Option<&[char]>) { + self.text.push(ch); + self.cell_count += 1; + self.style.len += ch.len_utf8(); + if let Some(chars) = zerowidth { + for c in chars { + self.text.push(*c); + self.style.len += c.len_utf8(); + } + } + } + + fn paint(&self, origin: Point, metrics: TerminalMetrics, window: &mut Window, cx: &mut App) { + let pos = point( + origin.x + metrics.cell_width * self.col as f32, + origin.y + metrics.line_height * self.row as f32, + ); + + window + .text_system() + .shape_line( + self.text.clone().into(), + self.font_size, + std::slice::from_ref(&self.style), + Some(metrics.cell_width), + ) + .paint( + pos, + metrics.line_height, + gpui::TextAlign::Left, + None, + window, + cx, + ) + .ok(); + } +} + +#[derive(Clone, Copy)] +struct CursorLayout { + row: usize, + col: usize, + shape: CursorShape, + color: Hsla, +} + +pub struct TerminalElement { + view: Entity, + focus_handle: FocusHandle, + snapshot: RenderSnapshot, + marked_text: Option, + font_family: &'static str, + font_size: Pixels, + line_height: Pixels, + cell_width: Pixels, +} + +pub struct PrepaintState { + bounds: Bounds, + metrics: TerminalMetrics, + rects: Vec, + runs: Vec, + cursor: Option, +} + +struct TerminalInputHandler { + view: Entity, + element_bounds: Bounds, +} + +impl InputHandler for TerminalInputHandler { + fn selected_text_range( + &mut self, + _ignore_disabled_input: bool, + _window: &mut Window, + cx: &mut App, + ) -> Option { + self.view.read(cx).terminal_accepts_text_input().then_some(UTF16Selection { + range: 0..0, + reversed: false, + }) + } + + fn marked_text_range( + &mut self, + _window: &mut Window, + cx: &mut App, + ) -> Option> { + self.view.read(cx).terminal_marked_text_range() + } + + fn text_for_range( + &mut self, + _range_utf16: std::ops::Range, + _adjusted_range: &mut Option>, + _window: &mut Window, + _cx: &mut App, + ) -> Option { + None + } + + fn replace_text_in_range( + &mut self, + _replacement_range: Option>, + text: &str, + window: &mut Window, + cx: &mut App, + ) { + self.view.update(cx, |view, cx| { + view.commit_terminal_ime_text(text, window, cx); + }); + } + + fn replace_and_mark_text_in_range( + &mut self, + _range_utf16: Option>, + new_text: &str, + _new_selected_range: Option>, + window: &mut Window, + cx: &mut App, + ) { + self.view.update(cx, |view, cx| { + view.set_terminal_marked_text(new_text.to_string(), window, cx); + }); + } + + fn unmark_text(&mut self, window: &mut Window, cx: &mut App) { + self.view.update(cx, |view, cx| { + view.clear_terminal_marked_text(window, cx); + }); + } + + fn bounds_for_range( + &mut self, + range_utf16: std::ops::Range, + _window: &mut Window, + cx: &mut App, + ) -> Option> { + self.view + .read(cx) + .terminal_ime_bounds_for_range(range_utf16, self.element_bounds) + } + + fn character_index_for_point( + &mut self, + _point: Point, + _window: &mut Window, + _cx: &mut App, + ) -> Option { + None + } + + fn accepts_text_input(&mut self, _window: &mut Window, cx: &mut App) -> bool { + self.view.read(cx).terminal_accepts_text_input() + } + + fn apple_press_and_hold_enabled(&mut self) -> bool { + false + } + + fn prefers_ime_for_printable_keys(&mut self, _window: &mut Window, cx: &mut App) -> bool { + self.view.read(cx).terminal_accepts_text_input() + } +} + +impl TerminalElement { + pub fn new( + view: Entity, + focus_handle: FocusHandle, + snapshot: RenderSnapshot, + marked_text: Option, + font_family: &'static str, + font_size: Pixels, + line_height: Pixels, + cell_width: Pixels, + ) -> Self { + Self { + view, + focus_handle, + snapshot, + marked_text, + font_family, + font_size, + line_height, + cell_width, + } + } + + fn base_text_style(&self, cx: &App) -> TextStyle { + TextStyle { + color: cx.theme().foreground, + font_family: self.font_family.into(), + font_size: self.font_size.into(), + line_height: self.line_height.into(), + ..Default::default() + } + } + + fn cell_run_style(&self, cell: &alacritty_terminal::term::cell::Cell, cx: &App) -> TextRun { + let mut fg = color_to_hsla(cell.fg, true, cx); + let mut bg = color_to_hsla(cell.bg, false, cx); + if cell.flags.contains(Flags::INVERSE) { + std::mem::swap(&mut fg, &mut bg); + } + if cell.flags.contains(Flags::DIM) { + fg.a *= 0.7; + } + + let underline = cell.flags.intersects(Flags::ALL_UNDERLINES).then(|| UnderlineStyle { + color: Some(fg), + thickness: px(1.0), + wavy: cell.flags.contains(Flags::UNDERCURL), + }); + let strikethrough = cell.flags.contains(Flags::STRIKEOUT).then(|| StrikethroughStyle { + color: Some(fg), + thickness: px(1.0), + }); + + let weight = if cell.flags.intersects(Flags::BOLD | Flags::DIM_BOLD) { + FontWeight::BOLD + } else { + FontWeight::NORMAL + }; + let style = if cell.flags.intersects(Flags::ITALIC | Flags::BOLD_ITALIC) { + FontStyle::Italic + } else { + FontStyle::Normal + }; + + TextRun { + len: cell.c.len_utf8(), + color: fg, + background_color: None, + font: Font { + family: self.font_family.into(), + weight, + style, + ..Font::default() + }, + underline, + strikethrough, + } + } + + fn layout_grid(&self, cx: &App) -> (Vec, Vec) { + let mut rects = Vec::new(); + let mut runs = Vec::new(); + let mut current_run: Option = None; + + for render_cell in &self.snapshot.cells { + let cell = &render_cell.cell; + if cell.flags.intersects( + Flags::HIDDEN | Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER, + ) { + continue; + } + + let selected = self + .snapshot + .selection + .is_some_and(|selection| selection_contains(selection, render_cell.row, render_cell.col)); + let bg = color_to_hsla(cell.bg, false, cx); + if selected || !is_default_bg(cell.bg) { + rects.push(LayoutRect { + row: render_cell.row, + col: render_cell.col, + cells: 1, + color: if selected { + cx.theme().selection + } else if cell.flags.contains(Flags::INVERSE) { + color_to_hsla(cell.fg, true, cx) + } else { + bg + }, + }); + } + + if is_blank(cell) { + if let Some(run) = current_run.take() { + runs.push(run); + } + continue; + } + + let style = self.cell_run_style(cell, cx); + if let Some(run) = current_run.as_mut() { + if run.can_append(&style, render_cell.row, render_cell.col) { + run.append(cell.c, cell.zerowidth()); + continue; + } + } + + if let Some(run) = current_run.take() { + runs.push(run); + } + + let mut run = BatchedTextRun::new( + render_cell.row, + render_cell.col, + cell.c, + style, + self.font_size, + ); + if let Some(chars) = cell.zerowidth() { + for ch in chars { + run.text.push(*ch); + run.style.len += ch.len_utf8(); + } + } + current_run = Some(run); + } + + if let Some(run) = current_run { + runs.push(run); + } + + (merge_rects(rects), runs) + } + + fn cursor_layout(&self, cx: &App) -> Option { + self.snapshot.cursor.map(|cursor| CursorLayout { + row: cursor.row, + col: cursor.col, + shape: cursor.shape, + color: cx.theme().primary, + }) + } +} + +impl IntoElement for TerminalElement { + type Element = Self; + + fn into_element(self) -> Self::Element { + self + } +} + +impl Element for TerminalElement { + type RequestLayoutState = (); + type PrepaintState = PrepaintState; + + fn id(&self) -> Option { + None + } + + fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { + None + } + + fn request_layout( + &mut self, + _id: Option<&GlobalElementId>, + _inspector_id: Option<&gpui::InspectorElementId>, + window: &mut Window, + cx: &mut App, + ) -> (LayoutId, Self::RequestLayoutState) { + let mut style = gpui::Style::default(); + style.size.width = relative(1.).into(); + style.size.height = relative(1.).into(); + (window.request_layout(style, None, cx), ()) + } + + fn prepaint( + &mut self, + _id: Option<&GlobalElementId>, + _inspector_id: Option<&gpui::InspectorElementId>, + bounds: Bounds, + _request_layout: &mut Self::RequestLayoutState, + _window: &mut Window, + cx: &mut App, + ) -> Self::PrepaintState { + let _ = self.base_text_style(cx); + let (rects, runs) = self.layout_grid(cx); + PrepaintState { + bounds, + metrics: TerminalMetrics { + cell_width: self.cell_width, + line_height: self.line_height, + }, + rects, + runs, + cursor: self.cursor_layout(cx), + } + } + + fn paint( + &mut self, + _id: Option<&GlobalElementId>, + _inspector_id: Option<&gpui::InspectorElementId>, + _bounds: Bounds, + _request_layout: &mut Self::RequestLayoutState, + prepaint: &mut Self::PrepaintState, + window: &mut Window, + cx: &mut App, + ) { + for rect in &prepaint.rects { + rect.paint(prepaint.bounds.origin, prepaint.metrics, window); + } + + for run in &prepaint.runs { + run.paint(prepaint.bounds.origin, prepaint.metrics, window, cx); + } + + window.handle_input( + &self.focus_handle, + TerminalInputHandler { + view: self.view.clone(), + element_bounds: prepaint.bounds, + }, + cx, + ); + + if let Some(marked_text) = self.marked_text.as_ref().filter(|text| !text.is_empty()) { + if let Some(cursor) = prepaint.cursor { + let pos = point( + prepaint.bounds.origin.x + prepaint.metrics.cell_width * cursor.col as f32, + prepaint.bounds.origin.y + prepaint.metrics.line_height * cursor.row as f32, + ); + let mut base_style = self.base_text_style(cx); + base_style.underline = Some(UnderlineStyle { + color: Some(base_style.color), + thickness: px(1.0), + wavy: false, + }); + let shaped = window.text_system().shape_line( + marked_text.clone().into(), + self.font_size, + &[TextRun { + len: marked_text.len(), + font: Font { + family: self.font_family.into(), + ..Font::default() + }, + color: base_style.color, + underline: base_style.underline, + ..Default::default() + }], + None, + ); + let bg_bounds = + Bounds::new(pos, gpui::size(shaped.width, prepaint.metrics.line_height)); + window.paint_quad(fill(bg_bounds, cx.theme().background)); + shaped + .paint( + pos, + prepaint.metrics.line_height, + gpui::TextAlign::Left, + None, + window, + cx, + ) + .ok(); + } + } + + if let Some(cursor) = prepaint.cursor { + if self.marked_text.as_ref().is_some_and(|text| !text.is_empty()) { + return; + } + let x = prepaint.bounds.origin.x + prepaint.metrics.cell_width * cursor.col as f32; + let y = prepaint.bounds.origin.y + prepaint.metrics.line_height * cursor.row as f32; + match cursor.shape { + CursorShape::Hidden => {} + CursorShape::Beam => { + window.paint_quad(fill( + Bounds::new(point(x, y), gpui::size(px(2.), prepaint.metrics.line_height)), + cursor.color, + )); + } + CursorShape::Underline => { + window.paint_quad(fill( + Bounds::new( + point(x, y + prepaint.metrics.line_height - px(2.)), + gpui::size(prepaint.metrics.cell_width, px(2.)), + ), + cursor.color, + )); + } + CursorShape::Block | CursorShape::HollowBlock => { + let alpha = if matches!(cursor.shape, CursorShape::HollowBlock) { + 0.18 + } else { + 0.32 + }; + window.paint_quad(fill( + Bounds::new( + point(x, y), + gpui::size(prepaint.metrics.cell_width, prepaint.metrics.line_height), + ), + cursor.color.opacity(alpha), + )); + } + } + } + } +} + +fn merge_rects(mut rects: Vec) -> Vec { + rects.sort_by_key(|rect| (rect.row, rect.col)); + let mut merged: Vec = Vec::with_capacity(rects.len()); + + for rect in rects { + if let Some(last) = merged.last_mut() { + if last.row == rect.row && last.color == rect.color && last.col + last.cells as i32 == rect.col { + last.cells += rect.cells; + continue; + } + } + merged.push(rect); + } + + merged +} + +fn selection_contains(selection: ViewportSelection, row: i32, col: i32) -> bool { + let row = row.max(0) as usize; + let col = col.max(0) as usize; + + if row < selection.start_row || row > selection.end_row { + return false; + } + + if selection.is_block { + return col >= selection.start_col && col <= selection.end_col; + } + + let after_start = row > selection.start_row || col >= selection.start_col; + let before_end = row < selection.end_row || col <= selection.end_col; + after_start && before_end +} + +fn is_blank(cell: &alacritty_terminal::term::cell::Cell) -> bool { + cell.c == ' ' && cell.zerowidth().is_none() && !cell.flags.intersects(Flags::ALL_UNDERLINES | Flags::STRIKEOUT) +} + +fn is_default_bg(color: AnsiColor) -> bool { + matches!(color, AnsiColor::Named(NamedColor::Background)) +} + +fn color_to_hsla(color: AnsiColor, foreground: bool, cx: &App) -> Hsla { + match color { + AnsiColor::Spec(rgb) => Hsla::from(Rgba { + r: rgb.r as f32 / 255.0, + g: rgb.g as f32 / 255.0, + b: rgb.b as f32 / 255.0, + a: 1.0, + }), + AnsiColor::Indexed(index) => ansi_index_color(index, cx), + AnsiColor::Named(named) => named_color(named, foreground, cx), + } +} + +fn ansi_index_color(index: u8, _cx: &App) -> Hsla { + const ANSI_16: [u32; 16] = [ + 0x1f2430, 0xff5c57, 0x5af78e, 0xf3f99d, 0x57c7ff, 0xff6ac1, 0x9aedfe, 0xf1f1f0, + 0x686868, 0xff5c57, 0x5af78e, 0xf3f99d, 0x57c7ff, 0xff6ac1, 0x9aedfe, 0xffffff, + ]; + + if (index as usize) < ANSI_16.len() { + return Hsla::from(rgb(ANSI_16[index as usize])); + } + + if index >= 232 { + let gray = 8 + (index - 232) * 10; + return Hsla::from(Rgba { + r: gray as f32 / 255.0, + g: gray as f32 / 255.0, + b: gray as f32 / 255.0, + a: 1.0, + }); + } + + let i = index - 16; + let r = i / 36; + let g = (i % 36) / 6; + let b = i % 6; + let conv = |v: u8| if v == 0 { 0 } else { 55 + v * 40 }; + Hsla::from(Rgba { + r: conv(r) as f32 / 255.0, + g: conv(g) as f32 / 255.0, + b: conv(b) as f32 / 255.0, + a: 1.0, + }) +} + +fn named_color(named: NamedColor, _foreground: bool, cx: &App) -> Hsla { + match named { + NamedColor::Foreground => cx.theme().foreground, + NamedColor::Background => cx.theme().background, + NamedColor::Black => Hsla::from(rgb(0x1f2430)), + NamedColor::Red => Hsla::from(rgb(0xff5c57)), + NamedColor::Green => Hsla::from(rgb(0x5af78e)), + NamedColor::Yellow => Hsla::from(rgb(0xf3f99d)), + NamedColor::Blue => Hsla::from(rgb(0x57c7ff)), + NamedColor::Magenta => Hsla::from(rgb(0xff6ac1)), + NamedColor::Cyan => Hsla::from(rgb(0x9aedfe)), + NamedColor::White => Hsla::from(rgb(0xf1f1f0)), + NamedColor::BrightBlack => Hsla::from(rgb(0x686868)), + NamedColor::BrightRed => Hsla::from(rgb(0xff5c57)), + NamedColor::BrightGreen => Hsla::from(rgb(0x5af78e)), + NamedColor::BrightYellow => Hsla::from(rgb(0xf3f99d)), + NamedColor::BrightBlue => Hsla::from(rgb(0x57c7ff)), + NamedColor::BrightMagenta => Hsla::from(rgb(0xff6ac1)), + NamedColor::BrightCyan => Hsla::from(rgb(0x9aedfe)), + NamedColor::BrightWhite => Hsla::from(rgb(0xffffff)), + NamedColor::Cursor => cx.theme().primary, + NamedColor::DimForeground => cx.theme().muted_foreground, + NamedColor::BrightForeground => cx.theme().foreground, + NamedColor::DimBlack => Hsla::from(rgb(0x3b4252)), + NamedColor::DimRed => Hsla::from(rgb(0xbf616a)), + NamedColor::DimGreen => Hsla::from(rgb(0xa3be8c)), + NamedColor::DimYellow => Hsla::from(rgb(0xebcb8b)), + NamedColor::DimBlue => Hsla::from(rgb(0x81a1c1)), + NamedColor::DimMagenta => Hsla::from(rgb(0xb48ead)), + NamedColor::DimCyan => Hsla::from(rgb(0x88c0d0)), + NamedColor::DimWhite => Hsla::from(rgb(0xe5e9f0)), + } +}