From 1235d0808ed7697874085fc8528f22aec988c85e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 15 Nov 2024 13:18:50 -0800 Subject: [PATCH 001/157] Use livekit's Rust SDK instead of their swift SDK (#13343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/livekit/rust-sdks/pull/355 Todo: * [x] make `call` / `live_kit_client` crates use the livekit rust sdk * [x] create a fake version of livekit rust API for integration tests * [x] capture local audio * [x] play remote audio * [x] capture local video tracks * [x] play remote video tracks * [x] tests passing * bugs * [x] deafening does not work (https://github.com/livekit/rust-sdks/issues/359) * [x] mute and speaking status are not replicated properly: (https://github.com/livekit/rust-sdks/issues/358) * [x] **linux** - crash due to symbol conflict between WebRTC's BoringSSL and libcurl's openssl (https://github.com/livekit/rust-sdks/issues/89) * [x] **linux** - libwebrtc-sys adds undesired dependencies on `libGL` and `libXext` * [x] **windows** - linker error, maybe related to the C++ stdlib (https://github.com/livekit/rust-sdks/issues/364) ``` libwebrtc_sys-54978c6ad5066a35.rlib(video_frame.obj) : error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MT_StaticRelease' doesn't match value 'MD_DynamicRelease' in libtree_sitter_yaml-df6b0adf8f009e8f.rlib(2e40c9e35e9506f4-scanner.o) ``` * [x] audio problems Release Notes: - Switch from Swift to Rust LiveKit SDK 🦀 --------- Co-authored-by: Mikayla Maki Co-authored-by: Conrad Irwin Co-authored-by: Kirill Bulatov Co-authored-by: Michael Sloan --- .cargo/config.toml | 6 + Cargo.lock | 434 +++++++- Cargo.toml | 6 + crates/call/Cargo.toml | 1 + crates/call/src/call.rs | 9 + crates/call/src/participant.rs | 28 +- crates/call/src/room.rs | 816 ++++++++------- crates/collab/src/tests.rs | 3 + .../collab/src/tests/channel_guest_tests.rs | 24 +- crates/collab/src/tests/following_tests.rs | 13 +- crates/collab/src/tests/integration_tests.rs | 27 +- crates/collab/src/tests/test_server.rs | 6 +- crates/collab_ui/src/collab_panel.rs | 5 +- crates/gpui/build.rs | 1 + crates/gpui/src/app.rs | 11 +- crates/gpui/src/app/test_context.rs | 10 +- crates/gpui/src/geometry.rs | 5 + crates/gpui/src/platform.rs | 26 + crates/gpui/src/platform/linux.rs | 2 + crates/gpui/src/platform/linux/platform.rs | 12 +- crates/gpui/src/platform/mac.rs | 5 + crates/gpui/src/platform/mac/platform.rs | 14 +- .../gpui/src/platform/mac/screen_capture.rs | 239 +++++ crates/gpui/src/platform/test.rs | 2 + crates/gpui/src/platform/test/platform.rs | 58 +- crates/gpui/src/platform/windows.rs | 2 + crates/gpui/src/platform/windows/platform.rs | 8 + crates/http_client/Cargo.toml | 2 +- crates/live_kit_client/Cargo.toml | 29 +- .../LiveKitBridge/Package.resolved | 52 - .../LiveKitBridge/Package.swift | 27 - .../live_kit_client/LiveKitBridge/README.md | 3 - .../Sources/LiveKitBridge/LiveKitBridge.swift | 383 ------- crates/live_kit_client/build.rs | 185 ---- crates/live_kit_client/examples/test_app.rs | 518 ++++++--- crates/live_kit_client/src/live_kit_client.rs | 406 +++++++- crates/live_kit_client/src/prod.rs | 981 ------------------ .../src/remote_video_track_view.rs | 61 ++ crates/live_kit_client/src/test.rs | 891 ++++++++-------- .../live_kit_client/src/test/participant.rs | 111 ++ .../live_kit_client/src/test/publication.rs | 116 +++ crates/live_kit_client/src/test/track.rs | 201 ++++ crates/live_kit_client/src/test/webrtc.rs | 136 +++ crates/media/Cargo.toml | 1 + crates/media/src/media.rs | 11 +- crates/title_bar/src/collab.rs | 65 +- crates/workspace/src/shared_screen.rs | 52 +- crates/workspace/src/workspace.rs | 13 +- 48 files changed, 3212 insertions(+), 2805 deletions(-) create mode 100644 crates/gpui/src/platform/mac/screen_capture.rs delete mode 100644 crates/live_kit_client/LiveKitBridge/Package.resolved delete mode 100644 crates/live_kit_client/LiveKitBridge/Package.swift delete mode 100644 crates/live_kit_client/LiveKitBridge/README.md delete mode 100644 crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift delete mode 100644 crates/live_kit_client/build.rs delete mode 100644 crates/live_kit_client/src/prod.rs create mode 100644 crates/live_kit_client/src/remote_video_track_view.rs create mode 100644 crates/live_kit_client/src/test/participant.rs create mode 100644 crates/live_kit_client/src/test/publication.rs create mode 100644 crates/live_kit_client/src/test/track.rs create mode 100644 crates/live_kit_client/src/test/webrtc.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index a657ae61b9..043adf6b30 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -13,6 +13,12 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"] linker = "clang" rustflags = ["-C", "link-arg=-fuse-ld=mold"] +[target.aarch64-apple-darwin] +rustflags = ["-C", "link-args=-Objc -all_load"] + +[target.x86_64-apple-darwin] +rustflags = ["-C", "link-args=-Objc -all_load"] + # This cfg will reduce the size of `windows::core::Error` from 16 bytes to 4 bytes [target.'cfg(target_os = "windows")'] rustflags = ["--cfg", "windows_slim_errors"] diff --git a/Cargo.lock b/Cargo.lock index 9feb65c458..0c4ea47526 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -915,6 +915,22 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "async-tungstenite" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" +dependencies = [ + "async-native-tls", + "async-std", + "async-tls", + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tungstenite 0.21.0", +] + [[package]] name = "async-tungstenite" version = "0.28.0" @@ -1789,7 +1805,7 @@ dependencies = [ "arrayvec", "cc", "cfg-if", - "constant_time_eq", + "constant_time_eq 0.3.1", ] [[package]] @@ -1975,6 +1991,27 @@ dependencies = [ "either", ] +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "call" version = "0.1.0" @@ -1983,6 +2020,7 @@ dependencies = [ "audio", "client", "collections", + "feature_flags", "fs", "futures 0.3.30", "gpui", @@ -2446,7 +2484,7 @@ dependencies = [ "anyhow", "async-native-tls", "async-recursion 0.3.2", - "async-tungstenite", + "async-tungstenite 0.28.0", "chrono", "clock", "cocoa 0.26.0", @@ -2579,7 +2617,7 @@ dependencies = [ "assistant", "async-stripe", "async-trait", - "async-tungstenite", + "async-tungstenite 0.28.0", "audio", "aws-config", "aws-sdk-kinesis", @@ -2630,7 +2668,7 @@ dependencies = [ "pretty_assertions", "project", "prometheus", - "prost", + "prost 0.9.0", "rand 0.8.5", "recent_projects", "release_channel", @@ -2831,6 +2869,12 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -3054,8 +3098,7 @@ dependencies = [ [[package]] name = "cpal" version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" +source = "git+https://github.com/zed-industries/cpal?rev=fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50#fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" dependencies = [ "alsa", "core-foundation-sys", @@ -3391,6 +3434,50 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +[[package]] +name = "cxx" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c042a0ba58aaff55299632834d1ea53ceff73d62373f62c9ae60890ad1b942" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45dc1c88d0fdac57518a9b1f6c4f4fb2aca8f3c30c0d03d7d8518b47ca0bcea6" +dependencies = [ + "cc", + "codespan-reporting", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.87", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa7ed7d30b289e2592cc55bc2ccd89803a63c913e008e6eb59f06cddf45bb52f" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c465d22de46b851c04630a5fc749a26005b263632ed2e0d9cc81518ead78d" +dependencies = [ + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.87", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -4654,6 +4741,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fsevent" version = "0.1.0" @@ -6139,6 +6236,15 @@ dependencies = [ "either", ] +[[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.12.1" @@ -6617,6 +6723,29 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libwebrtc" +version = "0.3.7" +source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" +dependencies = [ + "cxx", + "jni", + "js-sys", + "lazy_static", + "livekit-protocol", + "livekit-runtime", + "log", + "parking_lot", + "serde", + "serde_json", + "thiserror", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webrtc-sys", +] + [[package]] name = "libz-sys" version = "1.1.20" @@ -6629,6 +6758,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + [[package]] name = "linkify" version = "0.10.0" @@ -6675,13 +6813,16 @@ name = "live_kit_client" version = "0.1.0" dependencies = [ "anyhow", - "async-broadcast", "async-trait", "collections", "core-foundation 0.9.4", + "cpal", "futures 0.3.30", "gpui", + "http 0.2.12", + "http_client", "live_kit_server", + "livekit", "log", "media", "nanoid", @@ -6691,6 +6832,7 @@ dependencies = [ "serde_json", "sha2", "simplelog", + "util", ] [[package]] @@ -6701,13 +6843,88 @@ dependencies = [ "async-trait", "jsonwebtoken", "log", - "prost", - "prost-build", - "prost-types", + "prost 0.9.0", + "prost-build 0.9.0", + "prost-types 0.9.0", "reqwest 0.12.8", "serde", ] +[[package]] +name = "livekit" +version = "0.7.0" +source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" +dependencies = [ + "chrono", + "futures-util", + "lazy_static", + "libwebrtc", + "livekit-api", + "livekit-protocol", + "livekit-runtime", + "log", + "parking_lot", + "prost 0.12.6", + "semver", + "serde", + "serde_json", + "thiserror", + "tokio", +] + +[[package]] +name = "livekit-api" +version = "0.4.1" +source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" +dependencies = [ + "async-tungstenite 0.25.1", + "futures-util", + "http 0.2.12", + "jsonwebtoken", + "livekit-protocol", + "livekit-runtime", + "log", + "parking_lot", + "prost 0.12.6", + "reqwest 0.11.27", + "scopeguard", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", + "tokio-tungstenite 0.20.1", + "url", +] + +[[package]] +name = "livekit-protocol" +version = "0.3.6" +source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" +dependencies = [ + "futures-util", + "livekit-runtime", + "parking_lot", + "pbjson", + "pbjson-types", + "prost 0.12.6", + "prost-types 0.12.6", + "serde", + "thiserror", + "tokio", +] + +[[package]] +name = "livekit-runtime" +version = "0.3.1" +source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" +dependencies = [ + "async-io 2.3.4", + "async-std", + "async-task", + "futures 0.3.30", +] + [[package]] name = "lmdb-master-sys" version = "0.2.4" @@ -6993,6 +7210,7 @@ dependencies = [ "anyhow", "bindgen 0.70.1", "core-foundation 0.9.4", + "ctor", "foreign-types 0.5.0", "metal", "objc", @@ -7695,7 +7913,7 @@ dependencies = [ "md-5", "num", "num-bigint-dig", - "pbkdf2", + "pbkdf2 0.12.2", "rand 0.8.5", "serde", "sha2", @@ -8015,6 +8233,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "password-hash" version = "0.5.0" @@ -8065,6 +8294,55 @@ dependencies = [ "util", ] +[[package]] +name = "pbjson" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" +dependencies = [ + "base64 0.21.7", + "serde", +] + +[[package]] +name = "pbjson-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" +dependencies = [ + "heck 0.4.1", + "itertools 0.11.0", + "prost 0.12.6", + "prost-types 0.12.6", +] + +[[package]] +name = "pbjson-types" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" +dependencies = [ + "bytes 1.7.2", + "chrono", + "pbjson", + "pbjson-build", + "prost 0.12.6", + "prost-build 0.12.6", + "serde", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash 0.4.2", + "sha2", +] + [[package]] name = "pbkdf2" version = "0.12.2" @@ -9072,7 +9350,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes 1.7.2", - "prost-derive", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes 1.7.2", + "prost-derive 0.12.6", ] [[package]] @@ -9088,13 +9376,34 @@ dependencies = [ "log", "multimap", "petgraph", - "prost", - "prost-types", + "prost 0.9.0", + "prost-types 0.9.0", "regex", "tempfile", "which 4.4.2", ] +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes 1.7.2", + "heck 0.4.1", + "itertools 0.10.5", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.12.6", + "prost-types 0.12.6", + "regex", + "syn 2.0.87", + "tempfile", +] + [[package]] name = "prost-derive" version = "0.9.0" @@ -9108,6 +9417,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "prost-types" version = "0.9.0" @@ -9115,7 +9437,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ "bytes 1.7.2", - "prost", + "prost 0.9.0", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost 0.12.6", ] [[package]] @@ -9124,8 +9455,8 @@ version = "0.1.0" dependencies = [ "anyhow", "collections", - "prost", - "prost-build", + "prost 0.9.0", + "prost-build 0.9.0", "serde", ] @@ -9645,7 +9976,7 @@ dependencies = [ "log", "parking_lot", "paths", - "prost", + "prost 0.9.0", "release_channel", "rpc", "serde", @@ -9774,6 +10105,7 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.31", + "hyper-rustls 0.24.2", "hyper-tls", "ipnet", "js-sys", @@ -9783,6 +10115,8 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -9791,6 +10125,7 @@ dependencies = [ "system-configuration 0.5.1", "tokio", "tokio-native-tls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", @@ -10015,7 +10350,7 @@ name = "rpc" version = "0.1.0" dependencies = [ "anyhow", - "async-tungstenite", + "async-tungstenite 0.28.0", "base64 0.22.1", "chrono", "collections", @@ -10390,14 +10725,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scratch" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" + [[package]] name = "scrypt" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" dependencies = [ - "password-hash", - "pbkdf2", + "password-hash 0.5.0", + "pbkdf2 0.12.2", "salsa20", "sha2", ] @@ -12519,7 +12860,10 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", "tokio", + "tokio-rustls 0.24.1", "tungstenite 0.20.1", ] @@ -13027,6 +13371,7 @@ dependencies = [ "httparse", "log", "rand 0.8.5", + "rustls 0.21.12", "sha1", "thiserror", "url", @@ -13045,6 +13390,7 @@ dependencies = [ "http 1.1.0", "httparse", "log", + "native-tls", "rand 0.8.5", "sha1", "thiserror", @@ -14121,6 +14467,32 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webrtc-sys" +version = "0.3.5" +source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" +dependencies = [ + "cc", + "cxx", + "cxx-build", + "glob", + "log", + "webrtc-sys-build", +] + +[[package]] +name = "webrtc-sys-build" +version = "0.3.5" +source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" +dependencies = [ + "fs2", + "regex", + "reqwest 0.11.27", + "scratch", + "semver", + "zip", +] + [[package]] name = "weezl" version = "0.1.8" @@ -15504,6 +15876,26 @@ dependencies = [ "uuid", ] +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq 0.1.5", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2 0.11.0", + "sha1", + "time", + "zstd", +] + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/Cargo.toml b/Cargo.toml index c6d182fd58..9d86bc1b20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -363,6 +363,7 @@ heed = { version = "0.20.1", features = ["read-txn-no-tls"] } hex = "0.4.3" html5ever = "0.27.0" hyper = "0.14" +http = "1.1" ignore = "0.4.22" image = "0.25.1" indexmap = { version = "1.6.2", features = ["serde"] } @@ -371,6 +372,7 @@ itertools = "0.13.0" jsonwebtoken = "9.3" libc = "0.2" linkify = "0.10.0" +livekit = { git = "https://github.com/zed-industries/rust-sdks", rev="4262308983646ab5b0e0802c3d8bc52154f99aab", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false } log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] } markup5ever_rcdom = "0.3.0" nanoid = "0.4" @@ -549,6 +551,10 @@ features = [ "Win32_UI_WindowsAndMessaging", ] +# TODO livekit https://github.com/RustAudio/cpal/pull/891 +[patch.crates-io] +cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" } + [profile.dev] split-debuginfo = "unpacked" debug = "limited" diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index 974c860c08..3ac55078e7 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -27,6 +27,7 @@ anyhow.workspace = true audio.workspace = true client.workspace = true collections.workspace = true +feature_flags.workspace = true fs.workspace = true futures.workspace = true gpui.workspace = true diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index c7993f3658..eb91ff0885 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -18,6 +18,11 @@ use room::Event; use settings::Settings; use std::sync::Arc; +#[cfg(not(target_os = "windows"))] +pub use live_kit_client::play_remote_video_track; +pub use live_kit_client::{ + track::RemoteVideoTrack, RemoteVideoTrackView, RemoteVideoTrackViewEvent, +}; pub use participant::ParticipantLocation; pub use room::Room; @@ -26,6 +31,10 @@ struct GlobalActiveCall(Model); impl Global for GlobalActiveCall {} pub fn init(client: Arc, user_store: Model, cx: &mut AppContext) { + live_kit_client::init( + cx.background_executor().dispatcher.clone(), + cx.http_client(), + ); CallSettings::register(cx); let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx)); diff --git a/crates/call/src/participant.rs b/crates/call/src/participant.rs index 9faefc63c3..67bb95a3b7 100644 --- a/crates/call/src/participant.rs +++ b/crates/call/src/participant.rs @@ -1,13 +1,17 @@ +#![cfg_attr(target_os = "windows", allow(unused))] + use anyhow::{anyhow, Result}; -use client::ParticipantIndex; -use client::{proto, User}; +use client::{proto, ParticipantIndex, User}; use collections::HashMap; use gpui::WeakModel; -pub use live_kit_client::Frame; -pub use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack}; +use live_kit_client::AudioStream; use project::Project; use std::sync::Arc; +#[cfg(not(target_os = "windows"))] +pub use live_kit_client::id::TrackSid; +pub use live_kit_client::track::{RemoteAudioTrack, RemoteVideoTrack}; + #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ParticipantLocation { SharedProject { project_id: u64 }, @@ -39,7 +43,6 @@ pub struct LocalParticipant { pub role: proto::ChannelRole, } -#[derive(Clone, Debug)] pub struct RemoteParticipant { pub user: Arc, pub peer_id: proto::PeerId, @@ -49,6 +52,17 @@ pub struct RemoteParticipant { pub participant_index: ParticipantIndex, pub muted: bool, pub speaking: bool, - pub video_tracks: HashMap>, - pub audio_tracks: HashMap>, + #[cfg(not(target_os = "windows"))] + pub video_tracks: HashMap, + #[cfg(not(target_os = "windows"))] + pub audio_tracks: HashMap, +} + +impl RemoteParticipant { + pub fn has_video_tracks(&self) -> bool { + #[cfg(not(target_os = "windows"))] + return !self.video_tracks.is_empty(); + #[cfg(target_os = "windows")] + return false; + } } diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 3eb98f3109..a0e9de201a 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -1,3 +1,5 @@ +#![cfg_attr(target_os = "windows", allow(unused))] + use crate::{ call_settings::CallSettings, participant::{LocalParticipant, ParticipantLocation, RemoteParticipant}, @@ -15,11 +17,23 @@ use gpui::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, }; use language::LanguageRegistry; -use live_kit_client::{LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RoomUpdate}; +use live_kit_client as livekit; +#[cfg(not(target_os = "windows"))] +use livekit::{ + capture_local_audio_track, capture_local_video_track, + id::ParticipantIdentity, + options::{TrackPublishOptions, VideoCodec}, + play_remote_audio_track, + publication::LocalTrackPublication, + track::{TrackKind, TrackSource}, + RoomEvent, RoomOptions, +}; +#[cfg(target_os = "windows")] +use livekit::{publication::LocalTrackPublication, RoomEvent}; use postage::{sink::Sink, stream::Stream, watch}; use project::Project; use settings::Settings as _; -use std::{future::Future, mem, sync::Arc, time::Duration}; +use std::{any::Any, future::Future, mem, sync::Arc, time::Duration}; use util::{post_inc, ResultExt, TryFutureExt}; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); @@ -92,13 +106,10 @@ impl Room { !self.shared_projects.is_empty() } - #[cfg(any(test, feature = "test-support"))] + #[cfg(all(any(test, feature = "test-support"), not(target_os = "windows")))] pub fn is_connected(&self) -> bool { if let Some(live_kit) = self.live_kit.as_ref() { - matches!( - *live_kit.room.status().borrow(), - live_kit_client::ConnectionState::Connected { .. } - ) + live_kit.room.connection_state() == livekit::ConnectionState::Connected } else { false } @@ -112,77 +123,7 @@ impl Room { user_store: Model, cx: &mut ModelContext, ) -> Self { - let live_kit_room = if let Some(connection_info) = live_kit_connection_info { - let room = live_kit_client::Room::new(); - let mut status = room.status(); - // Consume the initial status of the room. - let _ = status.try_recv(); - let _maintain_room = cx.spawn(|this, mut cx| async move { - while let Some(status) = status.next().await { - let this = if let Some(this) = this.upgrade() { - this - } else { - break; - }; - - if status == live_kit_client::ConnectionState::Disconnected { - this.update(&mut cx, |this, cx| this.leave(cx).log_err()) - .ok(); - break; - } - } - }); - - let _handle_updates = cx.spawn({ - let room = room.clone(); - move |this, mut cx| async move { - let mut updates = room.updates(); - while let Some(update) = updates.next().await { - let this = if let Some(this) = this.upgrade() { - this - } else { - break; - }; - - this.update(&mut cx, |this, cx| { - this.live_kit_room_updated(update, cx).log_err() - }) - .ok(); - } - } - }); - - let connect = room.connect(&connection_info.server_url, &connection_info.token); - cx.spawn(|this, mut cx| async move { - connect.await?; - this.update(&mut cx, |this, cx| { - if this.can_use_microphone() { - if let Some(live_kit) = &this.live_kit { - if !live_kit.muted_by_user && !live_kit.deafened { - return this.share_microphone(cx); - } - } - } - Task::ready(Ok(())) - })? - .await - }) - .detach_and_log_err(cx); - - Some(LiveKitRoom { - room, - screen_track: LocalTrack::None, - microphone_track: LocalTrack::None, - next_publish_id: 0, - muted_by_user: Self::mute_on_join(cx), - deafened: false, - speaking: false, - _maintain_room, - _handle_updates, - }) - } else { - None - }; + spawn_room_connection(live_kit_connection_info, cx); let maintain_connection = cx.spawn({ let client = client.clone(); @@ -196,7 +137,7 @@ impl Room { Self { id, channel_id, - live_kit: live_kit_room, + live_kit: None, status: RoomStatus::Online, shared_projects: Default::default(), joined_projects: Default::default(), @@ -706,11 +647,45 @@ impl Room { this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))? } - fn apply_room_update( - &mut self, + fn apply_room_update(&mut self, room: proto::Room, cx: &mut ModelContext) -> Result<()> { + log::trace!( + "client {:?}. room update: {:?}", + self.client.user_id(), + &room + ); + + self.pending_room_update = Some(self.start_room_connection(room, cx)); + + cx.notify(); + Ok(()) + } + + pub fn room_update_completed(&mut self) -> impl Future { + let mut done_rx = self.room_update_completed_rx.clone(); + async move { + while let Some(result) = done_rx.next().await { + if result.is_some() { + break; + } + } + } + } + + #[cfg(target_os = "windows")] + fn start_room_connection( + &self, mut room: proto::Room, cx: &mut ModelContext, - ) -> Result<()> { + ) -> Task<()> { + Task::ready(()) + } + + #[cfg(not(target_os = "windows"))] + fn start_room_connection( + &self, + mut room: proto::Room, + cx: &mut ModelContext, + ) -> Task<()> { // Filter ourselves out from the room's participants. let local_participant_ix = room .participants @@ -737,8 +712,7 @@ impl Room { user_store.get_users(pending_participant_user_ids, cx), ) }); - - self.pending_room_update = Some(cx.spawn(|this, mut cx| async move { + cx.spawn(|this, mut cx| async move { let (remote_participants, pending_participants) = futures::join!(remote_participants, pending_participants); @@ -776,6 +750,11 @@ impl Room { this.local_participant.projects.clear(); } + let livekit_participants = this + .live_kit + .as_ref() + .map(|live_kit| live_kit.room.remote_participants()); + if let Some(participants) = remote_participants.log_err() { for (participant, user) in room.participants.into_iter().zip(participants) { let Some(peer_id) = participant.peer_id else { @@ -858,40 +837,31 @@ impl Room { muted: true, speaking: false, video_tracks: Default::default(), + #[cfg(not(target_os = "windows"))] audio_tracks: Default::default(), }, ); Audio::play_sound(Sound::Joined, cx); - - if let Some(live_kit) = this.live_kit.as_ref() { - let video_tracks = - live_kit.room.remote_video_tracks(&user.id.to_string()); - let audio_tracks = - live_kit.room.remote_audio_tracks(&user.id.to_string()); - let publications = live_kit - .room - .remote_audio_track_publications(&user.id.to_string()); - - for track in video_tracks { - this.live_kit_room_updated( - RoomUpdate::SubscribedToRemoteVideoTrack(track), - cx, - ) - .log_err(); - } - - for (track, publication) in - audio_tracks.iter().zip(publications.iter()) + if let Some(livekit_participants) = &livekit_participants { + if let Some(livekit_participant) = livekit_participants + .get(&ParticipantIdentity(user.id.to_string())) { - this.live_kit_room_updated( - RoomUpdate::SubscribedToRemoteAudioTrack( - track.clone(), - publication.clone(), - ), - cx, - ) - .log_err(); + for publication in + livekit_participant.track_publications().into_values() + { + if let Some(track) = publication.track() { + this.live_kit_room_updated( + RoomEvent::TrackSubscribed { + track, + publication, + participant: livekit_participant.clone(), + }, + cx, + ) + .warn_on_err(); + } + } } } } @@ -959,61 +929,89 @@ impl Room { cx.notify(); }) .ok(); - })); - - cx.notify(); - Ok(()) - } - - pub fn room_update_completed(&mut self) -> impl Future { - let mut done_rx = self.room_update_completed_rx.clone(); - async move { - while let Some(result) = done_rx.next().await { - if result.is_some() { - break; - } - } - } + }) } fn live_kit_room_updated( &mut self, - update: RoomUpdate, + event: RoomEvent, cx: &mut ModelContext, ) -> Result<()> { - match update { - RoomUpdate::SubscribedToRemoteVideoTrack(track) => { - let user_id = track.publisher_id().parse()?; - let track_id = track.sid().to_string(); - let participant = self - .remote_participants - .get_mut(&user_id) - .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; - participant.video_tracks.insert(track_id.clone(), track); - cx.emit(Event::RemoteVideoTracksChanged { - participant_id: participant.peer_id, - }); - } + log::trace!( + "client {:?}. livekit event: {:?}", + self.client.user_id(), + &event + ); - RoomUpdate::UnsubscribedFromRemoteVideoTrack { - publisher_id, - track_id, + match event { + #[cfg(not(target_os = "windows"))] + RoomEvent::TrackSubscribed { + track, + participant, + publication, } => { - let user_id = publisher_id.parse()?; - let participant = self - .remote_participants - .get_mut(&user_id) - .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?; - participant.video_tracks.remove(&track_id); - cx.emit(Event::RemoteVideoTracksChanged { - participant_id: participant.peer_id, - }); + let user_id = participant.identity().0.parse()?; + let track_id = track.sid(); + let participant = self.remote_participants.get_mut(&user_id).ok_or_else(|| { + anyhow!( + "{:?} subscribed to track by unknown participant {user_id}", + self.client.user_id() + ) + })?; + if self.live_kit.as_ref().map_or(true, |kit| kit.deafened) { + track.rtc_track().set_enabled(false); + } + match track { + livekit::track::RemoteTrack::Audio(track) => { + cx.emit(Event::RemoteAudioTracksChanged { + participant_id: participant.peer_id, + }); + let stream = play_remote_audio_track(&track, cx); + participant.audio_tracks.insert(track_id, (track, stream)); + participant.muted = publication.is_muted(); + } + livekit::track::RemoteTrack::Video(track) => { + cx.emit(Event::RemoteVideoTracksChanged { + participant_id: participant.peer_id, + }); + participant.video_tracks.insert(track_id, track); + } + } } - RoomUpdate::ActiveSpeakersChanged { speakers } => { + #[cfg(not(target_os = "windows"))] + RoomEvent::TrackUnsubscribed { + track, participant, .. + } => { + let user_id = participant.identity().0.parse()?; + let participant = self.remote_participants.get_mut(&user_id).ok_or_else(|| { + anyhow!( + "{:?}, unsubscribed from track by unknown participant {user_id}", + self.client.user_id() + ) + })?; + match track { + livekit::track::RemoteTrack::Audio(track) => { + participant.audio_tracks.remove(&track.sid()); + participant.muted = true; + cx.emit(Event::RemoteAudioTracksChanged { + participant_id: participant.peer_id, + }); + } + livekit::track::RemoteTrack::Video(track) => { + participant.video_tracks.remove(&track.sid()); + cx.emit(Event::RemoteVideoTracksChanged { + participant_id: participant.peer_id, + }); + } + } + } + + #[cfg(not(target_os = "windows"))] + RoomEvent::ActiveSpeakersChanged { speakers } => { let mut speaker_ids = speakers .into_iter() - .filter_map(|speaker_sid| speaker_sid.parse().ok()) + .filter_map(|speaker| speaker.identity().0.parse().ok()) .collect::>(); speaker_ids.sort_unstable(); for (sid, participant) in &mut self.remote_participants { @@ -1026,82 +1024,65 @@ impl Room { } } - RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } => { + #[cfg(not(target_os = "windows"))] + RoomEvent::TrackMuted { + participant, + publication, + } + | RoomEvent::TrackUnmuted { + participant, + publication, + } => { let mut found = false; - for participant in &mut self.remote_participants.values_mut() { - for track in participant.audio_tracks.values() { + let user_id = participant.identity().0.parse()?; + let track_id = publication.sid(); + if let Some(participant) = self.remote_participants.get_mut(&user_id) { + for (track, _) in participant.audio_tracks.values() { if track.sid() == track_id { found = true; break; } } if found { - participant.muted = muted; - break; + participant.muted = publication.is_muted(); } } } - RoomUpdate::SubscribedToRemoteAudioTrack(track, publication) => { - if let Some(live_kit) = &self.live_kit { - if live_kit.deafened { - track.stop(); - cx.foreground_executor() - .spawn(publication.set_enabled(false)) - .detach(); + #[cfg(not(target_os = "windows"))] + RoomEvent::LocalTrackUnpublished { publication, .. } => { + log::info!("unpublished track {}", publication.sid()); + if let Some(room) = &mut self.live_kit { + if let LocalTrack::Published { + track_publication, .. + } = &room.microphone_track + { + if track_publication.sid() == publication.sid() { + room.microphone_track = LocalTrack::None; + } + } + if let LocalTrack::Published { + track_publication, .. + } = &room.screen_track + { + if track_publication.sid() == publication.sid() { + room.screen_track = LocalTrack::None; + } } } - - let user_id = track.publisher_id().parse()?; - let track_id = track.sid().to_string(); - let participant = self - .remote_participants - .get_mut(&user_id) - .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; - participant.audio_tracks.insert(track_id.clone(), track); - participant.muted = publication.is_muted(); - - cx.emit(Event::RemoteAudioTracksChanged { - participant_id: participant.peer_id, - }); } - RoomUpdate::UnsubscribedFromRemoteAudioTrack { - publisher_id, - track_id, - } => { - let user_id = publisher_id.parse()?; - let participant = self - .remote_participants - .get_mut(&user_id) - .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?; - participant.audio_tracks.remove(&track_id); - cx.emit(Event::RemoteAudioTracksChanged { - participant_id: participant.peer_id, - }); + #[cfg(not(target_os = "windows"))] + RoomEvent::LocalTrackPublished { publication, .. } => { + log::info!("published track {:?}", publication.sid()); } - RoomUpdate::LocalAudioTrackUnpublished { publication } => { - log::info!("unpublished audio track {}", publication.sid()); - if let Some(room) = &mut self.live_kit { - room.microphone_track = LocalTrack::None; - } - } - - RoomUpdate::LocalVideoTrackUnpublished { publication } => { - log::info!("unpublished video track {}", publication.sid()); - if let Some(room) = &mut self.live_kit { - room.screen_track = LocalTrack::None; - } - } - - RoomUpdate::LocalAudioTrackPublished { publication } => { - log::info!("published audio track {}", publication.sid()); - } - - RoomUpdate::LocalVideoTrackPublished { publication } => { - log::info!("published video track {}", publication.sid()); + #[cfg(not(target_os = "windows"))] + RoomEvent::Disconnected { reason } => { + log::info!("disconnected from room: {reason:?}"); + self.leave(cx).detach_and_log_err(cx); } + _ => {} } cx.notify(); @@ -1317,8 +1298,17 @@ impl Room { self.live_kit.as_ref().map(|live_kit| live_kit.deafened) } - pub fn can_use_microphone(&self) -> bool { + pub fn can_use_microphone(&self, _cx: &AppContext) -> bool { use proto::ChannelRole::*; + + #[cfg(not(any(test, feature = "test-support")))] + { + use feature_flags::FeatureFlagAppExt as _; + if cfg!(target_os = "windows") || (cfg!(target_os = "linux") && !_cx.is_staff()) { + return false; + } + } + match self.local_participant.role { Admin | Member | Talker => true, Guest | Banned => false, @@ -1333,161 +1323,177 @@ impl Room { } } + #[cfg(target_os = "windows")] + pub fn share_microphone(&mut self, cx: &mut ModelContext) -> Task> { + Task::ready(Err(anyhow!("Windows is not supported yet"))) + } + + #[cfg(not(target_os = "windows"))] #[track_caller] pub fn share_microphone(&mut self, cx: &mut ModelContext) -> Task> { if self.status.is_offline() { return Task::ready(Err(anyhow!("room is offline"))); } - let publish_id = if let Some(live_kit) = self.live_kit.as_mut() { + let (participant, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { let publish_id = post_inc(&mut live_kit.next_publish_id); live_kit.microphone_track = LocalTrack::Pending { publish_id }; cx.notify(); - publish_id + (live_kit.room.local_participant(), publish_id) } else { return Task::ready(Err(anyhow!("live-kit was not initialized"))); }; cx.spawn(move |this, mut cx| async move { - let publish_track = async { - let track = LocalAudioTrack::create(); - this.upgrade() - .ok_or_else(|| anyhow!("room was dropped"))? - .update(&mut cx, |this, _| { - this.live_kit - .as_ref() - .map(|live_kit| live_kit.room.publish_audio_track(track)) - })? - .ok_or_else(|| anyhow!("live-kit was not initialized"))? - .await - }; - let publication = publish_track.await; - this.upgrade() - .ok_or_else(|| anyhow!("room was dropped"))? - .update(&mut cx, |this, cx| { - let live_kit = this - .live_kit - .as_mut() - .ok_or_else(|| anyhow!("live-kit was not initialized"))?; + let (track, stream) = cx.update(capture_local_audio_track)??; - let canceled = if let LocalTrack::Pending { - publish_id: cur_publish_id, - } = &live_kit.microphone_track - { - *cur_publish_id != publish_id - } else { - true - }; + let publication = participant + .publish_track( + livekit::track::LocalTrack::Audio(track), + TrackPublishOptions { + source: TrackSource::Microphone, + ..Default::default() + }, + ) + .await + .map_err(|error| anyhow!("failed to publish track: {error}")); + this.update(&mut cx, |this, cx| { + let live_kit = this + .live_kit + .as_mut() + .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - match publication { - Ok(publication) => { - if canceled { - live_kit.room.unpublish_track(publication); - } else { - if live_kit.muted_by_user || live_kit.deafened { - cx.background_executor() - .spawn(publication.set_mute(true)) - .detach(); - } - live_kit.microphone_track = LocalTrack::Published { - track_publication: publication, - }; - cx.notify(); + let canceled = if let LocalTrack::Pending { + publish_id: cur_publish_id, + } = &live_kit.microphone_track + { + *cur_publish_id != publish_id + } else { + true + }; + + match publication { + Ok(publication) => { + if canceled { + cx.background_executor() + .spawn(async move { + participant.unpublish_track(&publication.sid()).await + }) + .detach_and_log_err(cx) + } else { + if live_kit.muted_by_user || live_kit.deafened { + publication.mute(); } - Ok(()) + live_kit.microphone_track = LocalTrack::Published { + track_publication: publication, + _stream: Box::new(stream), + }; + cx.notify(); } - Err(error) => { - if canceled { - Ok(()) - } else { - live_kit.microphone_track = LocalTrack::None; - cx.notify(); - Err(error) - } + Ok(()) + } + Err(error) => { + if canceled { + Ok(()) + } else { + live_kit.microphone_track = LocalTrack::None; + cx.notify(); + Err(error) } } - })? + } + })? }) } + #[cfg(target_os = "windows")] + pub fn share_screen(&mut self, cx: &mut ModelContext) -> Task> { + Task::ready(Err(anyhow!("Windows is not supported yet"))) + } + + #[cfg(not(target_os = "windows"))] pub fn share_screen(&mut self, cx: &mut ModelContext) -> Task> { if self.status.is_offline() { return Task::ready(Err(anyhow!("room is offline"))); - } else if self.is_screen_sharing() { + } + if self.is_screen_sharing() { return Task::ready(Err(anyhow!("screen was already shared"))); } - let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { + let (participant, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { let publish_id = post_inc(&mut live_kit.next_publish_id); live_kit.screen_track = LocalTrack::Pending { publish_id }; cx.notify(); - (live_kit.room.display_sources(), publish_id) + (live_kit.room.local_participant(), publish_id) } else { return Task::ready(Err(anyhow!("live-kit was not initialized"))); }; + let sources = cx.screen_capture_sources(); + cx.spawn(move |this, mut cx| async move { - let publish_track = async { - let displays = displays.await?; - let display = displays - .first() - .ok_or_else(|| anyhow!("no display found"))?; - let track = LocalVideoTrack::screen_share_for_display(display); - this.upgrade() - .ok_or_else(|| anyhow!("room was dropped"))? - .update(&mut cx, |this, _| { - this.live_kit - .as_ref() - .map(|live_kit| live_kit.room.publish_video_track(track)) - })? - .ok_or_else(|| anyhow!("live-kit was not initialized"))? - .await - }; + let sources = sources.await??; + let source = sources.first().ok_or_else(|| anyhow!("no display found"))?; - let publication = publish_track.await; - this.upgrade() - .ok_or_else(|| anyhow!("room was dropped"))? - .update(&mut cx, |this, cx| { - let live_kit = this - .live_kit - .as_mut() - .ok_or_else(|| anyhow!("live-kit was not initialized"))?; + let (track, stream) = capture_local_video_track(&**source).await?; - let canceled = if let LocalTrack::Pending { - publish_id: cur_publish_id, - } = &live_kit.screen_track - { - *cur_publish_id != publish_id - } else { - true - }; + let publication = participant + .publish_track( + livekit::track::LocalTrack::Video(track), + TrackPublishOptions { + source: TrackSource::Screenshare, + video_codec: VideoCodec::H264, + ..Default::default() + }, + ) + .await + .map_err(|error| anyhow!("error publishing screen track {error:?}")); - match publication { - Ok(publication) => { - if canceled { - live_kit.room.unpublish_track(publication); - } else { - live_kit.screen_track = LocalTrack::Published { - track_publication: publication, - }; - cx.notify(); - } + this.update(&mut cx, |this, cx| { + let live_kit = this + .live_kit + .as_mut() + .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - Audio::play_sound(Sound::StartScreenshare, cx); + let canceled = if let LocalTrack::Pending { + publish_id: cur_publish_id, + } = &live_kit.screen_track + { + *cur_publish_id != publish_id + } else { + true + }; - Ok(()) + match publication { + Ok(publication) => { + if canceled { + cx.background_executor() + .spawn(async move { + participant.unpublish_track(&publication.sid()).await + }) + .detach() + } else { + live_kit.screen_track = LocalTrack::Published { + track_publication: publication, + _stream: Box::new(stream), + }; + cx.notify(); } - Err(error) => { - if canceled { - Ok(()) - } else { - live_kit.screen_track = LocalTrack::None; - cx.notify(); - Err(error) - } + + Audio::play_sound(Sound::StartScreenshare, cx); + Ok(()) + } + Err(error) => { + if canceled { + Ok(()) + } else { + live_kit.screen_track = LocalTrack::None; + cx.notify(); + Err(error) } } - })? + } + })? }) } @@ -1512,9 +1518,7 @@ impl Room { } if should_undeafen { - if let Some(task) = self.set_deafened(false, cx) { - task.detach_and_log_err(cx); - } + self.set_deafened(false, cx); } } } @@ -1527,9 +1531,7 @@ impl Room { live_kit.deafened = deafened; let should_change_mute = !live_kit.muted_by_user; - if let Some(task) = self.set_deafened(deafened, cx) { - task.detach_and_log_err(cx); - } + self.set_deafened(deafened, cx); if should_change_mute { if let Some(task) = self.set_mute(deafened, cx) { @@ -1557,47 +1559,36 @@ impl Room { LocalTrack::Published { track_publication, .. } => { - live_kit.room.unpublish_track(track_publication); - cx.notify(); - + #[cfg(not(target_os = "windows"))] + { + let local_participant = live_kit.room.local_participant(); + let sid = track_publication.sid(); + cx.background_executor() + .spawn(async move { local_participant.unpublish_track(&sid).await }) + .detach_and_log_err(cx); + cx.notify(); + } Audio::play_sound(Sound::StopScreenshare, cx); Ok(()) } } } - fn set_deafened( - &mut self, - deafened: bool, - cx: &mut ModelContext, - ) -> Option>> { - let live_kit = self.live_kit.as_mut()?; - cx.notify(); - - let mut track_updates = Vec::new(); - for participant in self.remote_participants.values() { - for publication in live_kit - .room - .remote_audio_track_publications(&participant.user.id.to_string()) - { - track_updates.push(publication.set_enabled(!deafened)); - } - - for track in participant.audio_tracks.values() { - if deafened { - track.stop(); - } else { - track.start(); + fn set_deafened(&mut self, deafened: bool, cx: &mut ModelContext) -> Option<()> { + #[cfg(not(target_os = "windows"))] + { + let live_kit = self.live_kit.as_mut()?; + cx.notify(); + for (_, participant) in live_kit.room.remote_participants() { + for (_, publication) in participant.track_publications() { + if publication.kind() == TrackKind::Audio { + publication.set_enabled(!deafened); + } } } } - Some(cx.foreground_executor().spawn(async move { - for result in futures::future::join_all(track_updates).await { - result?; - } - Ok(()) - })) + None } fn set_mute( @@ -1623,25 +1614,84 @@ impl Room { } } LocalTrack::Pending { .. } => None, - LocalTrack::Published { track_publication } => Some( - cx.foreground_executor() - .spawn(track_publication.set_mute(should_mute)), - ), + LocalTrack::Published { + track_publication, .. + } => { + #[cfg(not(target_os = "windows"))] + { + if should_mute { + track_publication.mute() + } else { + track_publication.unmute() + } + } + None + } } } +} - #[cfg(any(test, feature = "test-support"))] - pub fn set_display_sources(&self, sources: Vec) { - self.live_kit - .as_ref() - .unwrap() - .room - .set_display_sources(sources); +#[cfg(target_os = "windows")] +fn spawn_room_connection( + live_kit_connection_info: Option, + cx: &mut ModelContext<'_, Room>, +) { +} + +#[cfg(not(target_os = "windows"))] +fn spawn_room_connection( + live_kit_connection_info: Option, + cx: &mut ModelContext<'_, Room>, +) { + if let Some(connection_info) = live_kit_connection_info { + cx.spawn(|this, mut cx| async move { + let (room, mut events) = livekit::Room::connect( + &connection_info.server_url, + &connection_info.token, + RoomOptions::default(), + ) + .await?; + + this.update(&mut cx, |this, cx| { + let _handle_updates = cx.spawn(|this, mut cx| async move { + while let Some(event) = events.recv().await { + if this + .update(&mut cx, |this, cx| { + this.live_kit_room_updated(event, cx).warn_on_err(); + }) + .is_err() + { + break; + } + } + }); + + let muted_by_user = Room::mute_on_join(cx); + this.live_kit = Some(LiveKitRoom { + room: Arc::new(room), + screen_track: LocalTrack::None, + microphone_track: LocalTrack::None, + next_publish_id: 0, + muted_by_user, + deafened: false, + speaking: false, + _handle_updates, + }); + + if !muted_by_user && this.can_use_microphone(cx) { + this.share_microphone(cx) + } else { + Task::ready(Ok(())) + } + })? + .await + }) + .detach_and_log_err(cx); } } struct LiveKitRoom { - room: Arc, + room: Arc, screen_track: LocalTrack, microphone_track: LocalTrack, /// Tracks whether we're currently in a muted state due to auto-mute from deafening or manual mute performed by user. @@ -1649,17 +1699,21 @@ struct LiveKitRoom { deafened: bool, speaking: bool, next_publish_id: usize, - _maintain_room: Task<()>, _handle_updates: Task<()>, } impl LiveKitRoom { + #[cfg(target_os = "windows")] + fn stop_publishing(&mut self, _cx: &mut ModelContext) {} + + #[cfg(not(target_os = "windows"))] fn stop_publishing(&mut self, cx: &mut ModelContext) { + let mut tracks_to_unpublish = Vec::new(); if let LocalTrack::Published { track_publication, .. } = mem::replace(&mut self.microphone_track, LocalTrack::None) { - self.room.unpublish_track(track_publication); + tracks_to_unpublish.push(track_publication.sid()); cx.notify(); } @@ -1667,9 +1721,18 @@ impl LiveKitRoom { track_publication, .. } = mem::replace(&mut self.screen_track, LocalTrack::None) { - self.room.unpublish_track(track_publication); + tracks_to_unpublish.push(track_publication.sid()); cx.notify(); } + + let participant = self.room.local_participant(); + cx.background_executor() + .spawn(async move { + for sid in tracks_to_unpublish { + participant.unpublish_track(&sid).await.log_err(); + } + }) + .detach(); } } @@ -1680,6 +1743,7 @@ enum LocalTrack { }, Published { track_publication: LocalTrackPublication, + _stream: Box, }, } diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 29373bc6ea..2ce69efc9b 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -1,3 +1,6 @@ +// todo(windows): Actually run the tests +#![cfg(not(target_os = "windows"))] + use std::sync::Arc; use call::Room; diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 5a091fe308..006a3e5d1c 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -107,7 +107,9 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test }); assert!(project_b.read_with(cx_b, |project, cx| project.is_read_only(cx))); assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); - assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone())); + cx_b.update(|cx_b| { + assert!(room_b.read_with(cx_b, |room, cx| !room.can_use_microphone(cx))); + }); assert!(room_b .update(cx_b, |room, cx| room.share_microphone(cx)) .await @@ -133,7 +135,9 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx))); // B sees themselves as muted, and can unmute. - assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone())); + cx_b.update(|cx_b| { + assert!(room_b.read_with(cx_b, |room, cx| room.can_use_microphone(cx))); + }); room_b.read_with(cx_b, |room, _| assert!(room.is_muted())); room_b.update(cx_b, |room, cx| room.toggle_mute(cx)); cx_a.run_until_parked(); @@ -226,7 +230,9 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes let room_b = cx_b .read(ActiveCall::global) .update(cx_b, |call, _| call.room().unwrap().clone()); - assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone())); + cx_b.update(|cx_b| { + assert!(room_b.read_with(cx_b, |room, cx| !room.can_use_microphone(cx))); + }); // A tries to grant write access to B, but cannot because B has not // yet signed the zed CLA. @@ -244,7 +250,9 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes .unwrap_err(); cx_a.run_until_parked(); assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects())); - assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone())); + cx_b.update(|cx_b| { + assert!(room_b.read_with(cx_b, |room, cx| !room.can_use_microphone(cx))); + }); // A tries to grant write access to B, but cannot because B has not // yet signed the zed CLA. @@ -262,7 +270,9 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes .unwrap(); cx_a.run_until_parked(); assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects())); - assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone())); + cx_b.update(|cx_b| { + assert!(room_b.read_with(cx_b, |room, cx| room.can_use_microphone(cx))); + }); // User B signs the zed CLA. server @@ -287,5 +297,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes .unwrap(); cx_a.run_until_parked(); assert!(room_b.read_with(cx_b, |room, _| room.can_share_projects())); - assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone())); + cx_b.update(|cx_b| { + assert!(room_b.read_with(cx_b, |room, cx| room.can_use_microphone(cx))); + }); } diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index d708194f58..778d67b81d 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -9,10 +9,9 @@ use collab_ui::{ use editor::{Editor, ExcerptRange, MultiBuffer}; use gpui::{ point, BackgroundExecutor, BorrowAppContext, Context, Entity, SharedString, TestAppContext, - View, VisualContext, VisualTestContext, + TestScreenCaptureSource, View, VisualContext, VisualTestContext, }; use language::Capability; -use live_kit_client::MacOSDisplay; use project::WorktreeSettings; use rpc::proto::PeerId; use serde_json::json; @@ -429,17 +428,17 @@ async fn test_basic_following( ); // Client B activates an external window, which causes a new screen-sharing item to be added to the pane. - let display = MacOSDisplay::new(); + let display = TestScreenCaptureSource::new(); active_call_b .update(cx_b, |call, cx| call.set_location(None, cx)) .await .unwrap(); + cx_b.set_screen_capture_sources(vec![display]); active_call_b .update(cx_b, |call, cx| { - call.room().unwrap().update(cx, |room, cx| { - room.set_display_sources(vec![display.clone()]); - room.share_screen(cx) - }) + call.room() + .unwrap() + .update(cx, |room, cx| room.share_screen(cx)) }) .await .unwrap(); diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 5ec9a574a1..94b7ad81c5 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -15,7 +15,7 @@ use futures::{channel::mpsc, StreamExt as _}; use git::repository::GitFileStatus; use gpui::{ px, size, AppContext, BackgroundExecutor, Model, Modifiers, MouseButton, MouseDownEvent, - TestAppContext, UpdateGlobal, + TestAppContext, TestScreenCaptureSource, UpdateGlobal, }; use language::{ language_settings::{ @@ -24,7 +24,6 @@ use language::{ tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope, }; -use live_kit_client::MacOSDisplay; use lsp::LanguageServerId; use parking_lot::Mutex; use project::lsp_store::FormatTarget; @@ -241,15 +240,15 @@ async fn test_basic_calls( ); // User A shares their screen - let display = MacOSDisplay::new(); + let display = TestScreenCaptureSource::new(); let events_b = active_call_events(cx_b); let events_c = active_call_events(cx_c); + cx_a.set_screen_capture_sources(vec![display]); active_call_a .update(cx_a, |call, cx| { - call.room().unwrap().update(cx, |room, cx| { - room.set_display_sources(vec![display.clone()]); - room.share_screen(cx) - }) + call.room() + .unwrap() + .update(cx, |room, cx| room.share_screen(cx)) }) .await .unwrap(); @@ -1942,7 +1941,7 @@ async fn test_mute_deafen( room_a.read_with(cx_a, |room, _| assert!(!room.is_muted())); room_b.read_with(cx_b, |room, _| assert!(!room.is_muted())); - // Users A and B are both muted. + // Users A and B are both unmuted. assert_eq!( participant_audio_state(&room_a, cx_a), &[ParticipantAudioState { @@ -2074,7 +2073,7 @@ async fn test_mute_deafen( audio_tracks_playing: participant .audio_tracks .values() - .map(|track| track.is_playing()) + .map(|(track, _)| track.rtc_track().enabled()) .collect(), }) .collect::>() @@ -6057,13 +6056,13 @@ async fn test_join_call_after_screen_was_shared( assert_eq!(call_b.calling_user.github_login, "user_a"); // User A shares their screen - let display = MacOSDisplay::new(); + let display = TestScreenCaptureSource::new(); + cx_a.set_screen_capture_sources(vec![display]); active_call_a .update(cx_a, |call, cx| { - call.room().unwrap().update(cx, |room, cx| { - room.set_display_sources(vec![display.clone()]); - room.share_screen(cx) - }) + call.room() + .unwrap() + .update(cx, |room, cx| room.share_screen(cx)) }) .await .unwrap(); diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 17cd1b51c4..ae9e101031 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -47,7 +47,7 @@ use workspace::{Workspace, WorkspaceStore}; pub struct TestServer { pub app_state: Arc, - pub test_live_kit_server: Arc, + pub test_live_kit_server: Arc, server: Arc, next_github_user_id: i32, connection_killers: Arc>>>, @@ -89,7 +89,7 @@ impl TestServer { TestDb::sqlite(deterministic.clone()) }; let live_kit_server_id = NEXT_LIVE_KIT_SERVER_ID.fetch_add(1, SeqCst); - let live_kit_server = live_kit_client::TestServer::create( + let live_kit_server = live_kit_client::test::TestServer::create( format!("http://livekit.{}.test", live_kit_server_id), format!("devkey-{}", live_kit_server_id), format!("secret-{}", live_kit_server_id), @@ -499,7 +499,7 @@ impl TestServer { pub async fn build_app_state( test_db: &TestDb, - live_kit_test_server: &live_kit_client::TestServer, + live_kit_test_server: &live_kit_client::test::TestServer, executor: Executor, ) -> Arc { Arc::new(AppState { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index c93a48096a..fa3ab0219b 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -474,11 +474,10 @@ impl CollabPanel { project_id: project.id, worktree_root_names: project.worktree_root_names.clone(), host_user_id: participant.user.id, - is_last: projects.peek().is_none() - && participant.video_tracks.is_empty(), + is_last: projects.peek().is_none() && !participant.has_video_tracks(), }); } - if !participant.video_tracks.is_empty() { + if participant.has_video_tracks() { self.entries.push(ListEntry::ParticipantScreen { peer_id: Some(participant.peer_id), is_last: true, diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index 5a015106c7..e5917a0f05 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -48,6 +48,7 @@ mod macos { fn generate_dispatch_bindings() { println!("cargo:rustc-link-lib=framework=System"); + println!("cargo:rustc-link-lib=framework=ScreenCaptureKit"); println!("cargo:rerun-if-changed=src/platform/mac/dispatch.h"); let bindings = bindgen::Builder::default() diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 2a8da1c506..da14927fa2 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -33,8 +33,8 @@ use crate::{ Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation, - SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, - Window, WindowAppearance, WindowContext, WindowHandle, WindowId, + ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, + View, ViewContext, Window, WindowAppearance, WindowContext, WindowHandle, WindowId, }; mod async_context; @@ -599,6 +599,13 @@ impl AppContext { self.platform.primary_display() } + /// Returns a list of available screen capture sources. + pub fn screen_capture_sources( + &self, + ) -> oneshot::Receiver>>> { + self.platform.screen_capture_sources() + } + /// Returns the display with the given ID, if one exists. pub fn find_display(&self, id: DisplayId) -> Option> { self.displays() diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 34449c91ec..2f5053a382 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -4,8 +4,8 @@ use crate::{ Element, Empty, Entity, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher, - TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext, WindowBounds, - WindowContext, WindowHandle, WindowOptions, + TestPlatform, TestScreenCaptureSource, TestWindow, TextSystem, View, ViewContext, + VisualContext, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{channel::oneshot, Stream, StreamExt}; @@ -287,6 +287,12 @@ impl TestAppContext { self.test_window(window_handle).simulate_resize(size); } + /// Causes the given sources to be returned if the application queries for screen + /// capture sources. + pub fn set_screen_capture_sources(&self, sources: Vec) { + self.test_platform.set_screen_capture_sources(sources); + } + /// Returns all windows open in the test. pub fn windows(&self) -> Vec { self.app.borrow().windows().clone() diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index 9e0b9b9014..b636c95a61 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -704,6 +704,11 @@ pub struct Bounds { pub size: Size, } +/// Create a bounds with the given origin and size +pub fn bounds(origin: Point, size: Size) -> Bounds { + Bounds { origin, size } +} + impl Bounds { /// Generate a centered bounds for the given display or primary display if none is provided pub fn centered(display_id: Option, size: Size, cx: &AppContext) -> Self { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index a8424d197a..727ca952da 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -70,6 +70,9 @@ pub(crate) use test::*; #[cfg(target_os = "windows")] pub(crate) use windows::*; +#[cfg(any(test, feature = "test-support"))] +pub use test::TestScreenCaptureSource; + #[cfg(target_os = "macos")] pub(crate) fn current_platform(headless: bool) -> Rc { Rc::new(MacPlatform::new(headless)) @@ -149,6 +152,10 @@ pub(crate) trait Platform: 'static { None } + fn screen_capture_sources( + &self, + ) -> oneshot::Receiver>>>; + fn open_window( &self, handle: AnyWindowHandle, @@ -228,6 +235,25 @@ pub trait PlatformDisplay: Send + Sync + Debug { } } +/// A source of on-screen video content that can be captured. +pub trait ScreenCaptureSource { + /// Returns the video resolution of this source. + fn resolution(&self) -> Result>; + + /// Start capture video from this source, invoking the given callback + /// with each frame. + fn stream( + &self, + frame_callback: Box, + ) -> oneshot::Receiver>>; +} + +/// A video stream captured from a screen. +pub trait ScreenCaptureStream {} + +/// A frame of video captured from a screen. +pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame); + /// An opaque identifier for a hardware display #[derive(PartialEq, Eq, Hash, Copy, Clone)] pub struct DisplayId(pub(crate) u32); diff --git a/crates/gpui/src/platform/linux.rs b/crates/gpui/src/platform/linux.rs index 0499869361..089b52cf1e 100644 --- a/crates/gpui/src/platform/linux.rs +++ b/crates/gpui/src/platform/linux.rs @@ -20,3 +20,5 @@ pub(crate) use text_system::*; pub(crate) use wayland::*; #[cfg(feature = "x11")] pub(crate) use x11::*; + +pub(crate) type PlatformScreenCaptureFrame = (); diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index a2e9af691b..5865e50092 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -35,8 +35,8 @@ use crate::{ px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler, PlatformTextSystem, - PlatformWindow, Point, PromptLevel, Result, SemanticVersion, SharedString, Size, Task, - WindowAppearance, WindowOptions, WindowParams, + PlatformWindow, Point, PromptLevel, Result, ScreenCaptureSource, SemanticVersion, SharedString, + Size, Task, WindowAppearance, WindowOptions, WindowParams, }; pub(crate) const SCROLL_LINES: f32 = 3.0; @@ -242,6 +242,14 @@ impl Platform for P { self.displays() } + fn screen_capture_sources( + &self, + ) -> oneshot::Receiver>>> { + let (mut tx, rx) = oneshot::channel(); + tx.send(Err(anyhow!("screen capture not implemented"))).ok(); + rx + } + fn active_window(&self) -> Option { self.active_window() } diff --git a/crates/gpui/src/platform/mac.rs b/crates/gpui/src/platform/mac.rs index 396fd49d04..bd3d8f35ac 100644 --- a/crates/gpui/src/platform/mac.rs +++ b/crates/gpui/src/platform/mac.rs @@ -4,12 +4,14 @@ mod dispatcher; mod display; mod display_link; mod events; +mod screen_capture; #[cfg(not(feature = "macos-blade"))] mod metal_atlas; #[cfg(not(feature = "macos-blade"))] pub mod metal_renderer; +use media::core_video::CVImageBuffer; #[cfg(not(feature = "macos-blade"))] use metal_renderer as renderer; @@ -49,6 +51,9 @@ pub(crate) use window::*; #[cfg(feature = "font-kit")] pub(crate) use text_system::*; +/// A frame of video captured from a screen. +pub(crate) type PlatformScreenCaptureFrame = CVImageBuffer; + trait BoolExt { fn to_objc(self) -> BOOL; } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index b744c658ce..d0fd8a85f4 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -1,14 +1,14 @@ use super::{ attributed_string::{NSAttributedString, NSMutableAttributedString}, events::key_to_native, - BoolExt, + renderer, screen_capture, BoolExt, }; use crate::{ hash, Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString, CursorStyle, ForegroundExecutor, Image, ImageFormat, Keymap, MacDispatcher, MacDisplay, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, - PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowAppearance, - WindowParams, + PlatformTextSystem, PlatformWindow, Result, ScreenCaptureSource, SemanticVersion, Task, + WindowAppearance, WindowParams, }; use anyhow::anyhow; use block::ConcreteBlock; @@ -58,8 +58,6 @@ use std::{ }; use strum::IntoEnumIterator; -use super::renderer; - #[allow(non_upper_case_globals)] const NSUTF8StringEncoding: NSUInteger = 4; @@ -550,6 +548,12 @@ impl Platform for MacPlatform { .collect() } + fn screen_capture_sources( + &self, + ) -> oneshot::Receiver>>> { + screen_capture::get_sources() + } + fn active_window(&self) -> Option { MacWindow::active_window() } diff --git a/crates/gpui/src/platform/mac/screen_capture.rs b/crates/gpui/src/platform/mac/screen_capture.rs new file mode 100644 index 0000000000..a2b535996f --- /dev/null +++ b/crates/gpui/src/platform/mac/screen_capture.rs @@ -0,0 +1,239 @@ +use crate::{ + platform::{ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream}, + px, size, Pixels, Size, +}; +use anyhow::{anyhow, Result}; +use block::ConcreteBlock; +use cocoa::{ + base::{id, nil, YES}, + foundation::NSArray, +}; +use core_foundation::base::TCFType; +use ctor::ctor; +use futures::channel::oneshot; +use media::core_media::{CMSampleBuffer, CMSampleBufferRef}; +use metal::NSInteger; +use objc::{ + class, + declare::ClassDecl, + msg_send, + runtime::{Class, Object, Sel}, + sel, sel_impl, +}; +use std::{cell::RefCell, ffi::c_void, mem, ptr, rc::Rc}; + +#[derive(Clone)] +pub struct MacScreenCaptureSource { + sc_display: id, +} + +pub struct MacScreenCaptureStream { + sc_stream: id, + sc_stream_output: id, +} + +#[link(name = "ScreenCaptureKit", kind = "framework")] +extern "C" {} + +static mut DELEGATE_CLASS: *const Class = ptr::null(); +static mut OUTPUT_CLASS: *const Class = ptr::null(); +const FRAME_CALLBACK_IVAR: &str = "frame_callback"; + +#[allow(non_upper_case_globals)] +const SCStreamOutputTypeScreen: NSInteger = 0; + +impl ScreenCaptureSource for MacScreenCaptureSource { + fn resolution(&self) -> Result> { + unsafe { + let width: i64 = msg_send![self.sc_display, width]; + let height: i64 = msg_send![self.sc_display, height]; + Ok(size(px(width as f32), px(height as f32))) + } + } + + fn stream( + &self, + frame_callback: Box, + ) -> oneshot::Receiver>> { + unsafe { + let stream: id = msg_send![class!(SCStream), alloc]; + let filter: id = msg_send![class!(SCContentFilter), alloc]; + let configuration: id = msg_send![class!(SCStreamConfiguration), alloc]; + let delegate: id = msg_send![DELEGATE_CLASS, alloc]; + let output: id = msg_send![OUTPUT_CLASS, alloc]; + + let excluded_windows = NSArray::array(nil); + let filter: id = msg_send![filter, initWithDisplay:self.sc_display excludingWindows:excluded_windows]; + let configuration: id = msg_send![configuration, init]; + let delegate: id = msg_send![delegate, init]; + let output: id = msg_send![output, init]; + + output.as_mut().unwrap().set_ivar( + FRAME_CALLBACK_IVAR, + Box::into_raw(Box::new(frame_callback)) as *mut c_void, + ); + + let stream: id = msg_send![stream, initWithFilter:filter configuration:configuration delegate:delegate]; + + let (mut tx, rx) = oneshot::channel(); + + let mut error: id = nil; + let _: () = msg_send![stream, addStreamOutput:output type:SCStreamOutputTypeScreen sampleHandlerQueue:0 error:&mut error as *mut id]; + if error != nil { + let message: id = msg_send![error, localizedDescription]; + tx.send(Err(anyhow!("failed to add stream output {message:?}"))) + .ok(); + return rx; + } + + let tx = Rc::new(RefCell::new(Some(tx))); + let handler = ConcreteBlock::new({ + move |error: id| { + let result = if error == nil { + let stream = MacScreenCaptureStream { + sc_stream: stream, + sc_stream_output: output, + }; + Ok(Box::new(stream) as Box) + } else { + let message: id = msg_send![error, localizedDescription]; + Err(anyhow!("failed to stop screen capture stream {message:?}")) + }; + if let Some(tx) = tx.borrow_mut().take() { + tx.send(result).ok(); + } + } + }); + let handler = handler.copy(); + let _: () = msg_send![stream, startCaptureWithCompletionHandler:handler]; + rx + } + } +} + +impl Drop for MacScreenCaptureSource { + fn drop(&mut self) { + unsafe { + let _: () = msg_send![self.sc_display, release]; + } + } +} + +impl ScreenCaptureStream for MacScreenCaptureStream {} + +impl Drop for MacScreenCaptureStream { + fn drop(&mut self) { + unsafe { + let mut error: id = nil; + let _: () = msg_send![self.sc_stream, removeStreamOutput:self.sc_stream_output type:SCStreamOutputTypeScreen error:&mut error as *mut _]; + if error != nil { + let message: id = msg_send![error, localizedDescription]; + log::error!("failed to add stream output {message:?}"); + } + + let handler = ConcreteBlock::new(move |error: id| { + if error != nil { + let message: id = msg_send![error, localizedDescription]; + log::error!("failed to stop screen capture stream {message:?}"); + } + }); + let block = handler.copy(); + let _: () = msg_send![self.sc_stream, stopCaptureWithCompletionHandler:block]; + let _: () = msg_send![self.sc_stream, release]; + let _: () = msg_send![self.sc_stream_output, release]; + } + } +} + +pub(crate) fn get_sources() -> oneshot::Receiver>>> { + unsafe { + let (mut tx, rx) = oneshot::channel(); + let tx = Rc::new(RefCell::new(Some(tx))); + + let block = ConcreteBlock::new(move |shareable_content: id, error: id| { + let Some(mut tx) = tx.borrow_mut().take() else { + return; + }; + let result = if error == nil { + let displays: id = msg_send![shareable_content, displays]; + let mut result = Vec::new(); + for i in 0..displays.count() { + let display = displays.objectAtIndex(i); + let source = MacScreenCaptureSource { + sc_display: msg_send![display, retain], + }; + result.push(Box::new(source) as Box); + } + Ok(result) + } else { + let msg: id = msg_send![error, localizedDescription]; + Err(anyhow!("Failed to register: {:?}", msg)) + }; + tx.send(result).ok(); + }); + let block = block.copy(); + + let _: () = msg_send![ + class!(SCShareableContent), + getShareableContentExcludingDesktopWindows:YES + onScreenWindowsOnly:YES + completionHandler:block]; + rx + } +} + +#[ctor] +unsafe fn build_classes() { + let mut decl = ClassDecl::new("GPUIStreamDelegate", class!(NSObject)).unwrap(); + decl.add_method( + sel!(outputVideoEffectDidStartForStream:), + output_video_effect_did_start_for_stream as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(outputVideoEffectDidStopForStream:), + output_video_effect_did_stop_for_stream as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(stream:didStopWithError:), + stream_did_stop_with_error as extern "C" fn(&Object, Sel, id, id), + ); + DELEGATE_CLASS = decl.register(); + + let mut decl = ClassDecl::new("GPUIStreamOutput", class!(NSObject)).unwrap(); + decl.add_method( + sel!(stream:didOutputSampleBuffer:ofType:), + stream_did_output_sample_buffer_of_type as extern "C" fn(&Object, Sel, id, id, NSInteger), + ); + decl.add_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR); + + OUTPUT_CLASS = decl.register(); +} + +extern "C" fn output_video_effect_did_start_for_stream(_this: &Object, _: Sel, _stream: id) {} + +extern "C" fn output_video_effect_did_stop_for_stream(_this: &Object, _: Sel, _stream: id) {} + +extern "C" fn stream_did_stop_with_error(_this: &Object, _: Sel, _stream: id, _error: id) {} + +extern "C" fn stream_did_output_sample_buffer_of_type( + this: &Object, + _: Sel, + _stream: id, + sample_buffer: id, + buffer_type: NSInteger, +) { + if buffer_type != SCStreamOutputTypeScreen { + return; + } + + unsafe { + let sample_buffer = sample_buffer as CMSampleBufferRef; + let sample_buffer = CMSampleBuffer::wrap_under_get_rule(sample_buffer); + if let Some(buffer) = sample_buffer.image_buffer() { + let callback: Box> = + Box::from_raw(*this.get_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR) as *mut _); + callback(ScreenCaptureFrame(buffer)); + mem::forget(callback); + } + } +} diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index d17739239e..70462cb5e2 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -7,3 +7,5 @@ pub(crate) use dispatcher::*; pub(crate) use display::*; pub(crate) use platform::*; pub(crate) use window::*; + +pub use platform::TestScreenCaptureSource; diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index aadbe9b595..67227b60fe 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -1,7 +1,7 @@ use crate::{ - AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, Keymap, - Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow, WindowAppearance, - WindowParams, + px, size, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, + Keymap, Platform, PlatformDisplay, PlatformTextSystem, ScreenCaptureFrame, ScreenCaptureSource, + ScreenCaptureStream, Task, TestDisplay, TestWindow, WindowAppearance, WindowParams, }; use anyhow::Result; use collections::VecDeque; @@ -31,6 +31,7 @@ pub(crate) struct TestPlatform { #[cfg(any(target_os = "linux", target_os = "freebsd"))] current_primary_item: Mutex>, pub(crate) prompts: RefCell, + screen_capture_sources: RefCell>, pub opened_url: RefCell>, pub text_system: Arc, #[cfg(target_os = "windows")] @@ -38,6 +39,31 @@ pub(crate) struct TestPlatform { weak: Weak, } +#[derive(Clone)] +/// A fake screen capture source, used for testing. +pub struct TestScreenCaptureSource {} + +pub struct TestScreenCaptureStream {} + +impl ScreenCaptureSource for TestScreenCaptureSource { + fn resolution(&self) -> Result> { + Ok(size(px(1.), px(1.))) + } + + fn stream( + &self, + _frame_callback: Box, + ) -> oneshot::Receiver>> { + let (mut tx, rx) = oneshot::channel(); + let stream = TestScreenCaptureStream {}; + tx.send(Ok(Box::new(stream) as Box)) + .ok(); + rx + } +} + +impl ScreenCaptureStream for TestScreenCaptureStream {} + #[derive(Default)] pub(crate) struct TestPrompts { multiple_choice: VecDeque>, @@ -72,6 +98,7 @@ impl TestPlatform { background_executor: executor, foreground_executor, prompts: Default::default(), + screen_capture_sources: Default::default(), active_cursor: Default::default(), active_display: Rc::new(TestDisplay::new()), active_window: Default::default(), @@ -114,6 +141,10 @@ impl TestPlatform { !self.prompts.borrow().multiple_choice.is_empty() } + pub(crate) fn set_screen_capture_sources(&self, sources: Vec) { + *self.screen_capture_sources.borrow_mut() = sources; + } + pub(crate) fn prompt(&self, msg: &str, detail: Option<&str>) -> oneshot::Receiver { let (tx, rx) = oneshot::channel(); self.background_executor() @@ -202,6 +233,20 @@ impl Platform for TestPlatform { Some(self.active_display.clone()) } + fn screen_capture_sources( + &self, + ) -> oneshot::Receiver>>> { + let (mut tx, rx) = oneshot::channel(); + tx.send(Ok(self + .screen_capture_sources + .borrow() + .iter() + .map(|source| Box::new(source.clone()) as Box) + .collect())) + .ok(); + rx + } + fn active_window(&self) -> Option { self.active_window .borrow() @@ -330,6 +375,13 @@ impl Platform for TestPlatform { } } +impl TestScreenCaptureSource { + /// Create a fake screen capture source, for testing. + pub fn new() -> Self { + Self {} + } +} + #[cfg(target_os = "windows")] impl Drop for TestPlatform { fn drop(&mut self) { diff --git a/crates/gpui/src/platform/windows.rs b/crates/gpui/src/platform/windows.rs index 84cf107c70..51d09f0013 100644 --- a/crates/gpui/src/platform/windows.rs +++ b/crates/gpui/src/platform/windows.rs @@ -21,3 +21,5 @@ pub(crate) use window::*; pub(crate) use wrapper::*; pub(crate) use windows::Win32::Foundation::HWND; + +pub(crate) type PlatformScreenCaptureFrame = (); diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 29443afabb..c2bbc9890c 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -325,6 +325,14 @@ impl Platform for WindowsPlatform { WindowsDisplay::primary_monitor().map(|display| Rc::new(display) as Rc) } + fn screen_capture_sources( + &self, + ) -> oneshot::Receiver>>> { + let (mut tx, rx) = oneshot::channel(); + tx.send(Err(anyhow!("screen capture not implemented"))).ok(); + rx + } + fn active_window(&self) -> Option { let active_window_hwnd = unsafe { GetActiveWindow() }; self.try_get_windows_inner_from_hwnd(active_window_hwnd) diff --git a/crates/http_client/Cargo.toml b/crates/http_client/Cargo.toml index ac8e254b84..a4f10cff18 100644 --- a/crates/http_client/Cargo.toml +++ b/crates/http_client/Cargo.toml @@ -20,7 +20,7 @@ bytes.workspace = true anyhow.workspace = true derive_more.workspace = true futures.workspace = true -http = "1.1" +http.workspace = true log.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/live_kit_client/Cargo.toml b/crates/live_kit_client/Cargo.toml index e23c63453e..921c048f23 100644 --- a/crates/live_kit_client/Cargo.toml +++ b/crates/live_kit_client/Cargo.toml @@ -2,7 +2,7 @@ name = "live_kit_client" version = "0.1.0" edition = "2021" -description = "Bindings to LiveKit Swift client SDK" +description = "Logic for using LiveKit with GPUI" publish = false license = "GPL-3.0-or-later" @@ -19,42 +19,37 @@ name = "test_app" [features] no-webrtc = [] test-support = [ - "async-trait", "collections/test-support", "gpui/test-support", - "live_kit_server", "nanoid", ] [dependencies] anyhow.workspace = true -async-broadcast = "0.7" -async-trait = { workspace = true, optional = true } -collections = { workspace = true, optional = true } +async-trait.workspace = true +collections.workspace = true +cpal = "0.15" futures.workspace = true -gpui = { workspace = true, optional = true } -live_kit_server = { workspace = true, optional = true } +gpui.workspace = true +http_2 = { package = "http", version = "0.2.1" } +live_kit_server.workspace = true log.workspace = true media.workspace = true nanoid = { workspace = true, optional = true} parking_lot.workspace = true postage.workspace = true +util.workspace = true +http_client.workspace = true + +[target.'cfg(not(target_os = "windows"))'.dependencies] +livekit.workspace = true [target.'cfg(target_os = "macos")'.dependencies] core-foundation.workspace = true -[target.'cfg(all(not(target_os = "macos")))'.dependencies] -async-trait = { workspace = true } -collections = { workspace = true } -gpui = { workspace = true } -live_kit_server.workspace = true -nanoid.workspace = true - [dev-dependencies] -async-trait.workspace = true collections = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } -live_kit_server.workspace = true nanoid.workspace = true sha2.workspace = true simplelog.workspace = true diff --git a/crates/live_kit_client/LiveKitBridge/Package.resolved b/crates/live_kit_client/LiveKitBridge/Package.resolved deleted file mode 100644 index b925bc8f0d..0000000000 --- a/crates/live_kit_client/LiveKitBridge/Package.resolved +++ /dev/null @@ -1,52 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "LiveKit", - "repositoryURL": "https://github.com/livekit/client-sdk-swift.git", - "state": { - "branch": null, - "revision": "7331b813a5ab8a95cfb81fb2b4ed10519428b9ff", - "version": "1.0.12" - } - }, - { - "package": "Promises", - "repositoryURL": "https://github.com/google/promises.git", - "state": { - "branch": null, - "revision": "ec957ccddbcc710ccc64c9dcbd4c7006fcf8b73a", - "version": "2.2.0" - } - }, - { - "package": "WebRTC", - "repositoryURL": "https://github.com/webrtc-sdk/Specs.git", - "state": { - "branch": null, - "revision": "2f6bab30c8df0fe59ab3e58bc99097f757f85f65", - "version": "104.5112.17" - } - }, - { - "package": "swift-log", - "repositoryURL": "https://github.com/apple/swift-log.git", - "state": { - "branch": null, - "revision": "32e8d724467f8fe623624570367e3d50c5638e46", - "version": "1.5.2" - } - }, - { - "package": "SwiftProtobuf", - "repositoryURL": "https://github.com/apple/swift-protobuf.git", - "state": { - "branch": null, - "revision": "ce20dc083ee485524b802669890291c0d8090170", - "version": "1.22.1" - } - } - ] - }, - "version": 1 -} diff --git a/crates/live_kit_client/LiveKitBridge/Package.swift b/crates/live_kit_client/LiveKitBridge/Package.swift deleted file mode 100644 index d7b5c271b9..0000000000 --- a/crates/live_kit_client/LiveKitBridge/Package.swift +++ /dev/null @@ -1,27 +0,0 @@ -// swift-tools-version: 5.5 - -import PackageDescription - -let package = Package( - name: "LiveKitBridge", - platforms: [ - .macOS(.v10_15) - ], - products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. - .library( - name: "LiveKitBridge", - type: .static, - targets: ["LiveKitBridge"]), - ], - dependencies: [ - .package(url: "https://github.com/livekit/client-sdk-swift.git", .exact("1.0.12")), - ], - targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. - .target( - name: "LiveKitBridge", - dependencies: [.product(name: "LiveKit", package: "client-sdk-swift")]), - ] -) diff --git a/crates/live_kit_client/LiveKitBridge/README.md b/crates/live_kit_client/LiveKitBridge/README.md deleted file mode 100644 index b982c67286..0000000000 --- a/crates/live_kit_client/LiveKitBridge/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# LiveKitBridge - -A description of this package. diff --git a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift deleted file mode 100644 index 7468c08791..0000000000 --- a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift +++ /dev/null @@ -1,383 +0,0 @@ -import Foundation -import LiveKit -import WebRTC -import ScreenCaptureKit - -class LKRoomDelegate: RoomDelegate { - var data: UnsafeRawPointer - var onDidDisconnect: @convention(c) (UnsafeRawPointer) -> Void - var onDidSubscribeToRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void - var onDidUnsubscribeFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void - var onMuteChangedFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void - var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void - var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void - var onDidUnsubscribeFromRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void - var onDidPublishOrUnpublishLocalAudioTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void - var onDidPublishOrUnpublishLocalVideoTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void - - init( - data: UnsafeRawPointer, - onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void, - onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void, - onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, - onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, - onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void, - onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, - onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, - onDidPublishOrUnpublishLocalAudioTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void, - onDidPublishOrUnpublishLocalVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void - ) - { - self.data = data - self.onDidDisconnect = onDidDisconnect - self.onDidSubscribeToRemoteAudioTrack = onDidSubscribeToRemoteAudioTrack - self.onDidUnsubscribeFromRemoteAudioTrack = onDidUnsubscribeFromRemoteAudioTrack - self.onDidSubscribeToRemoteVideoTrack = onDidSubscribeToRemoteVideoTrack - self.onDidUnsubscribeFromRemoteVideoTrack = onDidUnsubscribeFromRemoteVideoTrack - self.onMuteChangedFromRemoteAudioTrack = onMuteChangedFromRemoteAudioTrack - self.onActiveSpeakersChanged = onActiveSpeakersChanged - self.onDidPublishOrUnpublishLocalAudioTrack = onDidPublishOrUnpublishLocalAudioTrack - self.onDidPublishOrUnpublishLocalVideoTrack = onDidPublishOrUnpublishLocalVideoTrack - } - - func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) { - if connectionState.isDisconnected { - self.onDidDisconnect(self.data) - } - } - - func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) { - if track.kind == .video { - self.onDidSubscribeToRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque()) - } else if track.kind == .audio { - self.onDidSubscribeToRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque(), Unmanaged.passUnretained(publication).toOpaque()) - } - } - - func room(_ room: Room, participant: Participant, didUpdate publication: TrackPublication, muted: Bool) { - if publication.kind == .audio { - self.onMuteChangedFromRemoteAudioTrack(self.data, publication.sid as CFString, muted) - } - } - - func room(_ room: Room, didUpdate speakers: [Participant]) { - guard let speaker_ids = speakers.compactMap({ $0.identity as CFString }) as CFArray? else { return } - self.onActiveSpeakersChanged(self.data, speaker_ids) - } - - func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) { - if track.kind == .video { - self.onDidUnsubscribeFromRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString) - } else if track.kind == .audio { - self.onDidUnsubscribeFromRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString) - } - } - - func room(_ room: Room, localParticipant: LocalParticipant, didPublish publication: LocalTrackPublication) { - if publication.kind == .video { - self.onDidPublishOrUnpublishLocalVideoTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), true) - } else if publication.kind == .audio { - self.onDidPublishOrUnpublishLocalAudioTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), true) - } - } - - func room(_ room: Room, localParticipant: LocalParticipant, didUnpublish publication: LocalTrackPublication) { - if publication.kind == .video { - self.onDidPublishOrUnpublishLocalVideoTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), false) - } else if publication.kind == .audio { - self.onDidPublishOrUnpublishLocalAudioTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), false) - } - } -} - -class LKVideoRenderer: NSObject, VideoRenderer { - var data: UnsafeRawPointer - var onFrame: @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool - var onDrop: @convention(c) (UnsafeRawPointer) -> Void - var adaptiveStreamIsEnabled: Bool = false - var adaptiveStreamSize: CGSize = .zero - weak var track: VideoTrack? - - init(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) { - self.data = data - self.onFrame = onFrame - self.onDrop = onDrop - } - - deinit { - self.onDrop(self.data) - } - - func setSize(_ size: CGSize) { - } - - func renderFrame(_ frame: RTCVideoFrame?) { - let buffer = frame?.buffer as? RTCCVPixelBuffer - if let pixelBuffer = buffer?.pixelBuffer { - if !self.onFrame(self.data, pixelBuffer) { - DispatchQueue.main.async { - self.track?.remove(videoRenderer: self) - } - } - } - } -} - -@_cdecl("LKRoomDelegateCreate") -public func LKRoomDelegateCreate( - data: UnsafeRawPointer, - onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void, - onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void, - onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, - onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, - onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void, - onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, - onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, - onDidPublishOrUnpublishLocalAudioTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void, - onDidPublishOrUnpublishLocalVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void -) -> UnsafeMutableRawPointer { - let delegate = LKRoomDelegate( - data: data, - onDidDisconnect: onDidDisconnect, - onDidSubscribeToRemoteAudioTrack: onDidSubscribeToRemoteAudioTrack, - onDidUnsubscribeFromRemoteAudioTrack: onDidUnsubscribeFromRemoteAudioTrack, - onMuteChangedFromRemoteAudioTrack: onMuteChangedFromRemoteAudioTrack, - onActiveSpeakersChanged: onActiveSpeakerChanged, - onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack, - onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack, - onDidPublishOrUnpublishLocalAudioTrack: onDidPublishOrUnpublishLocalAudioTrack, - onDidPublishOrUnpublishLocalVideoTrack: onDidPublishOrUnpublishLocalVideoTrack - ) - return Unmanaged.passRetained(delegate).toOpaque() -} - -@_cdecl("LKRoomCreate") -public func LKRoomCreate(delegate: UnsafeRawPointer) -> UnsafeMutableRawPointer { - let delegate = Unmanaged.fromOpaque(delegate).takeUnretainedValue() - return Unmanaged.passRetained(Room(delegate: delegate)).toOpaque() -} - -@_cdecl("LKRoomConnect") -public func LKRoomConnect(room: UnsafeRawPointer, url: CFString, token: CFString, callback: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, callback_data: UnsafeRawPointer) { - let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - - room.connect(url as String, token as String).then { _ in - callback(callback_data, UnsafeRawPointer(nil) as! CFString?) - }.catch { error in - callback(callback_data, error.localizedDescription as CFString) - } -} - -@_cdecl("LKRoomDisconnect") -public func LKRoomDisconnect(room: UnsafeRawPointer) { - let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - room.disconnect() -} - -@_cdecl("LKRoomPublishVideoTrack") -public func LKRoomPublishVideoTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, UnsafeMutableRawPointer?, CFString?) -> Void, callback_data: UnsafeRawPointer) { - let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - let track = Unmanaged.fromOpaque(track).takeUnretainedValue() - room.localParticipant?.publishVideoTrack(track: track).then { publication in - callback(callback_data, Unmanaged.passRetained(publication).toOpaque(), nil) - }.catch { error in - callback(callback_data, nil, error.localizedDescription as CFString) - } -} - -@_cdecl("LKRoomPublishAudioTrack") -public func LKRoomPublishAudioTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, UnsafeMutableRawPointer?, CFString?) -> Void, callback_data: UnsafeRawPointer) { - let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - let track = Unmanaged.fromOpaque(track).takeUnretainedValue() - room.localParticipant?.publishAudioTrack(track: track).then { publication in - callback(callback_data, Unmanaged.passRetained(publication).toOpaque(), nil) - }.catch { error in - callback(callback_data, nil, error.localizedDescription as CFString) - } -} - - -@_cdecl("LKRoomUnpublishTrack") -public func LKRoomUnpublishTrack(room: UnsafeRawPointer, publication: UnsafeRawPointer) { - let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() - let _ = room.localParticipant?.unpublish(publication: publication) -} - -@_cdecl("LKRoomAudioTracksForRemoteParticipant") -public func LKRoomAudioTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { - let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - - for (_, participant) in room.remoteParticipants { - if participant.identity == participantId as String { - return participant.audioTracks.compactMap { $0.track as? RemoteAudioTrack } as CFArray? - } - } - - return nil; -} - -@_cdecl("LKRoomAudioTrackPublicationsForRemoteParticipant") -public func LKRoomAudioTrackPublicationsForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { - let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - - for (_, participant) in room.remoteParticipants { - if participant.identity == participantId as String { - return participant.audioTracks.compactMap { $0 as? RemoteTrackPublication } as CFArray? - } - } - - return nil; -} - -@_cdecl("LKRoomVideoTracksForRemoteParticipant") -public func LKRoomVideoTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { - let room = Unmanaged.fromOpaque(room).takeUnretainedValue() - - for (_, participant) in room.remoteParticipants { - if participant.identity == participantId as String { - return participant.videoTracks.compactMap { $0.track as? RemoteVideoTrack } as CFArray? - } - } - - return nil; -} - -@_cdecl("LKLocalAudioTrackCreateTrack") -public func LKLocalAudioTrackCreateTrack() -> UnsafeMutableRawPointer { - let track = LocalAudioTrack.createTrack(options: AudioCaptureOptions( - echoCancellation: true, - noiseSuppression: true - )) - - return Unmanaged.passRetained(track).toOpaque() -} - - -@_cdecl("LKCreateScreenShareTrackForDisplay") -public func LKCreateScreenShareTrackForDisplay(display: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { - let display = Unmanaged.fromOpaque(display).takeUnretainedValue() - let track = LocalVideoTrack.createMacOSScreenShareTrack(source: display, preferredMethod: .legacy) - return Unmanaged.passRetained(track).toOpaque() -} - -@_cdecl("LKVideoRendererCreate") -public func LKVideoRendererCreate(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer { - Unmanaged.passRetained(LKVideoRenderer(data: data, onFrame: onFrame, onDrop: onDrop)).toOpaque() -} - -@_cdecl("LKVideoTrackAddRenderer") -public func LKVideoTrackAddRenderer(track: UnsafeRawPointer, renderer: UnsafeRawPointer) { - let track = Unmanaged.fromOpaque(track).takeUnretainedValue() as! VideoTrack - let renderer = Unmanaged.fromOpaque(renderer).takeRetainedValue() - renderer.track = track - track.add(videoRenderer: renderer) -} - -@_cdecl("LKRemoteVideoTrackGetSid") -public func LKRemoteVideoTrackGetSid(track: UnsafeRawPointer) -> CFString { - let track = Unmanaged.fromOpaque(track).takeUnretainedValue() - return track.sid! as CFString -} - -@_cdecl("LKRemoteAudioTrackGetSid") -public func LKRemoteAudioTrackGetSid(track: UnsafeRawPointer) -> CFString { - let track = Unmanaged.fromOpaque(track).takeUnretainedValue() - return track.sid! as CFString -} - -@_cdecl("LKRemoteAudioTrackStart") -public func LKRemoteAudioTrackStart(track: UnsafeRawPointer) { - let track = Unmanaged.fromOpaque(track).takeUnretainedValue() - track.start() -} - -@_cdecl("LKRemoteAudioTrackStop") -public func LKRemoteAudioTrackStop(track: UnsafeRawPointer) { - let track = Unmanaged.fromOpaque(track).takeUnretainedValue() - track.stop() -} - -@_cdecl("LKDisplaySources") -public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFArray?, CFString?) -> Void) { - MacOSScreenCapturer.sources(for: .display, includeCurrentApplication: false, preferredMethod: .legacy).then { displaySources in - callback(data, displaySources as CFArray, nil) - }.catch { error in - callback(data, nil, error.localizedDescription as CFString) - } -} - -@_cdecl("LKLocalTrackPublicationSetMute") -public func LKLocalTrackPublicationSetMute( - publication: UnsafeRawPointer, - muted: Bool, - on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, - callback_data: UnsafeRawPointer -) { - let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() - - if muted { - publication.mute().then { - on_complete(callback_data, nil) - }.catch { error in - on_complete(callback_data, error.localizedDescription as CFString) - } - } else { - publication.unmute().then { - on_complete(callback_data, nil) - }.catch { error in - on_complete(callback_data, error.localizedDescription as CFString) - } - } -} - -@_cdecl("LKLocalTrackPublicationIsMuted") -public func LKLocalTrackPublicationIsMuted( - publication: UnsafeRawPointer -) -> Bool { - let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() - return publication.muted -} - -@_cdecl("LKRemoteTrackPublicationSetEnabled") -public func LKRemoteTrackPublicationSetEnabled( - publication: UnsafeRawPointer, - enabled: Bool, - on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, - callback_data: UnsafeRawPointer -) { - let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() - - publication.set(enabled: enabled).then { - on_complete(callback_data, nil) - }.catch { error in - on_complete(callback_data, error.localizedDescription as CFString) - } -} - -@_cdecl("LKRemoteTrackPublicationIsMuted") -public func LKRemoteTrackPublicationIsMuted( - publication: UnsafeRawPointer -) -> Bool { - let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() - - return publication.muted -} - -@_cdecl("LKRemoteTrackPublicationGetSid") -public func LKRemoteTrackPublicationGetSid( - publication: UnsafeRawPointer -) -> CFString { - let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() - - return publication.sid as CFString -} - -@_cdecl("LKLocalTrackPublicationGetSid") -public func LKLocalTrackPublicationGetSid( - publication: UnsafeRawPointer -) -> CFString { - let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() - - return publication.sid as CFString -} diff --git a/crates/live_kit_client/build.rs b/crates/live_kit_client/build.rs deleted file mode 100644 index 2fdfd982bf..0000000000 --- a/crates/live_kit_client/build.rs +++ /dev/null @@ -1,185 +0,0 @@ -use serde::Deserialize; -use std::{ - env, - path::{Path, PathBuf}, - process::Command, -}; - -const SWIFT_PACKAGE_NAME: &str = "LiveKitBridge"; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SwiftTargetInfo { - pub triple: String, - pub unversioned_triple: String, - pub module_triple: String, - pub swift_runtime_compatibility_version: String, - #[serde(rename = "librariesRequireRPath")] - pub libraries_require_rpath: bool, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SwiftPaths { - pub runtime_library_paths: Vec, - pub runtime_library_import_paths: Vec, - pub runtime_resource_path: String, -} - -#[derive(Debug, Deserialize)] -pub struct SwiftTarget { - pub target: SwiftTargetInfo, - pub paths: SwiftPaths, -} - -const MACOS_TARGET_VERSION: &str = "10.15.7"; - -fn main() { - if cfg!(all( - target_os = "macos", - not(any(test, feature = "test-support", feature = "no-webrtc")), - )) { - let swift_target = get_swift_target(); - - build_bridge(&swift_target); - link_swift_stdlib(&swift_target); - link_webrtc_framework(&swift_target); - - // Register exported Objective-C selectors, protocols, etc when building example binaries. - println!("cargo:rustc-link-arg=-Wl,-ObjC"); - } -} - -fn build_bridge(swift_target: &SwiftTarget) { - println!("cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET"); - println!("cargo:rerun-if-changed={}/Sources", SWIFT_PACKAGE_NAME); - println!( - "cargo:rerun-if-changed={}/Package.swift", - SWIFT_PACKAGE_NAME - ); - println!( - "cargo:rerun-if-changed={}/Package.resolved", - SWIFT_PACKAGE_NAME - ); - - let swift_package_root = swift_package_root(); - let swift_target_folder = swift_target_folder(); - let swift_cache_folder = swift_cache_folder(); - if !Command::new("swift") - .arg("build") - .arg("--disable-automatic-resolution") - .args(["--configuration", &env::var("PROFILE").unwrap()]) - .args(["--triple", &swift_target.target.triple]) - .args(["--build-path".into(), swift_target_folder]) - .args(["--cache-path".into(), swift_cache_folder]) - .current_dir(&swift_package_root) - .status() - .unwrap() - .success() - { - panic!( - "Failed to compile swift package in {}", - swift_package_root.display() - ); - } - - println!( - "cargo:rustc-link-search=native={}", - swift_target.out_dir_path().display() - ); - println!("cargo:rustc-link-lib=static={}", SWIFT_PACKAGE_NAME); -} - -fn link_swift_stdlib(swift_target: &SwiftTarget) { - for path in &swift_target.paths.runtime_library_paths { - println!("cargo:rustc-link-search=native={}", path); - } -} - -fn link_webrtc_framework(swift_target: &SwiftTarget) { - let swift_out_dir_path = swift_target.out_dir_path(); - println!("cargo:rustc-link-lib=framework=WebRTC"); - println!( - "cargo:rustc-link-search=framework={}", - swift_out_dir_path.display() - ); - // Find WebRTC.framework as a sibling of the executable when running tests. - println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path"); - // Find WebRTC.framework in parent directory of the executable when running examples. - println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/.."); - - let source_path = swift_out_dir_path.join("WebRTC.framework"); - let deps_dir_path = - PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../../deps/WebRTC.framework"); - let target_dir_path = - PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../../WebRTC.framework"); - copy_dir(&source_path, &deps_dir_path); - copy_dir(&source_path, &target_dir_path); -} - -fn get_swift_target() -> SwiftTarget { - let mut arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - if arch == "aarch64" { - arch = "arm64".into(); - } - let target = format!("{}-apple-macosx{}", arch, MACOS_TARGET_VERSION); - - let swift_target_info_str = Command::new("swift") - .args(["-target", &target, "-print-target-info"]) - .output() - .unwrap() - .stdout; - - serde_json::from_slice(&swift_target_info_str).unwrap() -} - -fn swift_package_root() -> PathBuf { - env::current_dir().unwrap().join(SWIFT_PACKAGE_NAME) -} - -fn swift_target_folder() -> PathBuf { - let target = env::var("TARGET").unwrap(); - env::current_dir() - .unwrap() - .join(format!("../../target/{target}/{SWIFT_PACKAGE_NAME}_target")) -} - -fn swift_cache_folder() -> PathBuf { - let target = env::var("TARGET").unwrap(); - env::current_dir() - .unwrap() - .join(format!("../../target/{target}/{SWIFT_PACKAGE_NAME}_cache")) -} - -fn copy_dir(source: &Path, destination: &Path) { - assert!( - Command::new("rm") - .arg("-rf") - .arg(destination) - .status() - .unwrap() - .success(), - "could not remove {:?} before copying", - destination - ); - - assert!( - Command::new("cp") - .arg("-R") - .args([source, destination]) - .status() - .unwrap() - .success(), - "could not copy {:?} to {:?}", - source, - destination - ); -} - -impl SwiftTarget { - fn out_dir_path(&self) -> PathBuf { - swift_target_folder() - .join(&self.target.unversioned_triple) - .join(env::var("PROFILE").unwrap()) - } -} diff --git a/crates/live_kit_client/examples/test_app.rs b/crates/live_kit_client/examples/test_app.rs index de8be97e86..8edd171d84 100644 --- a/crates/live_kit_client/examples/test_app.rs +++ b/crates/live_kit_client/examples/test_app.rs @@ -1,18 +1,53 @@ -use std::time::Duration; +#![cfg_attr(windows, allow(unused))] + +use gpui::{ + actions, bounds, div, point, + prelude::{FluentBuilder as _, IntoElement}, + px, rgb, size, AsyncAppContext, Bounds, InteractiveElement, KeyBinding, Menu, MenuItem, + ParentElement, Pixels, Render, ScreenCaptureStream, SharedString, + StatefulInteractiveElement as _, Styled, Task, View, ViewContext, VisualContext, WindowBounds, + WindowHandle, WindowOptions, +}; +#[cfg(not(target_os = "windows"))] +use live_kit_client::{ + capture_local_audio_track, capture_local_video_track, + id::ParticipantIdentity, + options::{TrackPublishOptions, VideoCodec}, + participant::{Participant, RemoteParticipant}, + play_remote_audio_track, + publication::{LocalTrackPublication, RemoteTrackPublication}, + track::{LocalTrack, RemoteTrack, RemoteVideoTrack, TrackSource}, + AudioStream, RemoteVideoTrackView, Room, RoomEvent, RoomOptions, +}; + +#[cfg(target_os = "windows")] +use live_kit_client::{ + participant::{Participant, RemoteParticipant}, + publication::{LocalTrackPublication, RemoteTrackPublication}, + track::{LocalTrack, RemoteTrack, RemoteVideoTrack}, + AudioStream, RemoteVideoTrackView, Room, RoomEvent, +}; -use futures::StreamExt; -use gpui::{actions, KeyBinding, Menu, MenuItem}; -use live_kit_client::{LocalAudioTrack, LocalVideoTrack, Room, RoomUpdate}; use live_kit_server::token::{self, VideoGrant}; use log::LevelFilter; +use postage::stream::Stream as _; use simplelog::SimpleLogger; actions!(live_kit_client, [Quit]); +#[cfg(windows)] +fn main() {} + +#[cfg(not(windows))] fn main() { SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); gpui::App::new().run(|cx| { + live_kit_client::init( + cx.background_executor().dispatcher.clone(), + cx.http_client(), + ); + #[cfg(any(test, feature = "test-support"))] println!("USING TEST LIVEKIT"); @@ -20,10 +55,8 @@ fn main() { println!("USING REAL LIVEKIT"); cx.activate(true); - cx.on_action(quit); cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]); - cx.set_menus(vec![Menu { name: "Zed".into(), items: vec![MenuItem::Action { @@ -36,132 +69,26 @@ fn main() { let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into()); let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into()); let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into()); + let height = px(800.); + let width = px(800.); cx.spawn(|cx| async move { - let user_a_token = token::create( - &live_kit_key, - &live_kit_secret, - Some("test-participant-1"), - VideoGrant::to_join("test-room"), - ) - .unwrap(); - let room_a = Room::new(); - room_a.connect(&live_kit_url, &user_a_token).await.unwrap(); - - let user2_token = token::create( - &live_kit_key, - &live_kit_secret, - Some("test-participant-2"), - VideoGrant::to_join("test-room"), - ) - .unwrap(); - let room_b = Room::new(); - room_b.connect(&live_kit_url, &user2_token).await.unwrap(); - - let mut room_updates = room_b.updates(); - let audio_track = LocalAudioTrack::create(); - let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap(); - - if let RoomUpdate::SubscribedToRemoteAudioTrack(track, _) = - room_updates.next().await.unwrap() - { - let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); - assert_eq!(remote_tracks.len(), 1); - assert_eq!(remote_tracks[0].publisher_id(), "test-participant-1"); - assert_eq!(track.publisher_id(), "test-participant-1"); - } else { - panic!("unexpected message"); - } - - audio_track_publication.set_mute(true).await.unwrap(); - - println!("waiting for mute changed!"); - if let RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } = - room_updates.next().await.unwrap() - { - let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); - assert_eq!(remote_tracks[0].sid(), track_id); - assert!(muted); - } else { - panic!("unexpected message"); - } - - audio_track_publication.set_mute(false).await.unwrap(); - - if let RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } = - room_updates.next().await.unwrap() - { - let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); - assert_eq!(remote_tracks[0].sid(), track_id); - assert!(!muted); - } else { - panic!("unexpected message"); - } - - println!("Pausing for 5 seconds to test audio, make some noise!"); - let timer = cx.background_executor().timer(Duration::from_secs(5)); - timer.await; - let remote_audio_track = room_b - .remote_audio_tracks("test-participant-1") - .pop() + let mut windows = Vec::new(); + for i in 0..3 { + let token = token::create( + &live_kit_key, + &live_kit_secret, + Some(&format!("test-participant-{i}")), + VideoGrant::to_join("test-room"), + ) .unwrap(); - room_a.unpublish_track(audio_track_publication); - // Clear out any active speakers changed messages - let mut next = room_updates.next().await.unwrap(); - while let RoomUpdate::ActiveSpeakersChanged { speakers } = next { - println!("Speakers changed: {:?}", speakers); - next = room_updates.next().await.unwrap(); + let bounds = bounds(point(width * i, px(0.0)), size(width, height)); + let window = + LivekitWindow::new(live_kit_url.as_str(), token.as_str(), bounds, cx.clone()) + .await; + windows.push(window); } - - if let RoomUpdate::UnsubscribedFromRemoteAudioTrack { - publisher_id, - track_id, - } = next - { - assert_eq!(publisher_id, "test-participant-1"); - assert_eq!(remote_audio_track.sid(), track_id); - assert_eq!(room_b.remote_audio_tracks("test-participant-1").len(), 0); - } else { - panic!("unexpected message"); - } - - let displays = room_a.display_sources().await.unwrap(); - let display = displays.into_iter().next().unwrap(); - - let local_video_track = LocalVideoTrack::screen_share_for_display(&display); - let local_video_track_publication = - room_a.publish_video_track(local_video_track).await.unwrap(); - - if let RoomUpdate::SubscribedToRemoteVideoTrack(track) = - room_updates.next().await.unwrap() - { - let remote_video_tracks = room_b.remote_video_tracks("test-participant-1"); - assert_eq!(remote_video_tracks.len(), 1); - assert_eq!(remote_video_tracks[0].publisher_id(), "test-participant-1"); - assert_eq!(track.publisher_id(), "test-participant-1"); - } else { - panic!("unexpected message"); - } - - let remote_video_track = room_b - .remote_video_tracks("test-participant-1") - .pop() - .unwrap(); - room_a.unpublish_track(local_video_track_publication); - if let RoomUpdate::UnsubscribedFromRemoteVideoTrack { - publisher_id, - track_id, - } = room_updates.next().await.unwrap() - { - assert_eq!(publisher_id, "test-participant-1"); - assert_eq!(remote_video_track.sid(), track_id); - assert_eq!(room_b.remote_video_tracks("test-participant-1").len(), 0); - } else { - panic!("unexpected message"); - } - - cx.update(|cx| cx.shutdown()).ok(); }) .detach(); }); @@ -170,3 +97,340 @@ fn main() { fn quit(_: &Quit, cx: &mut gpui::AppContext) { cx.quit(); } + +struct LivekitWindow { + room: Room, + microphone_track: Option, + screen_share_track: Option, + microphone_stream: Option, + screen_share_stream: Option>, + #[cfg(not(target_os = "windows"))] + remote_participants: Vec<(ParticipantIdentity, ParticipantState)>, + _events_task: Task<()>, +} + +#[derive(Default)] +struct ParticipantState { + audio_output_stream: Option<(RemoteTrackPublication, AudioStream)>, + muted: bool, + screen_share_output_view: Option<(RemoteVideoTrack, View)>, + speaking: bool, +} + +#[cfg(not(windows))] +impl LivekitWindow { + async fn new( + url: &str, + token: &str, + bounds: Bounds, + cx: AsyncAppContext, + ) -> WindowHandle { + let (room, mut events) = Room::connect(url, token, RoomOptions::default()) + .await + .unwrap(); + + cx.update(|cx| { + cx.open_window( + WindowOptions { + window_bounds: Some(WindowBounds::Windowed(bounds)), + ..Default::default() + }, + |cx| { + cx.new_view(|cx| { + let _events_task = cx.spawn(|this, mut cx| async move { + while let Some(event) = events.recv().await { + this.update(&mut cx, |this: &mut LivekitWindow, cx| { + this.handle_room_event(event, cx) + }) + .ok(); + } + }); + + Self { + room, + microphone_track: None, + microphone_stream: None, + screen_share_track: None, + screen_share_stream: None, + remote_participants: Vec::new(), + _events_task, + } + }) + }, + ) + .unwrap() + }) + .unwrap() + } + + fn handle_room_event(&mut self, event: RoomEvent, cx: &mut ViewContext) { + eprintln!("event: {event:?}"); + + match event { + RoomEvent::TrackUnpublished { + publication, + participant, + } => { + let output = self.remote_participant(participant); + let unpublish_sid = publication.sid(); + if output + .audio_output_stream + .as_ref() + .map_or(false, |(track, _)| track.sid() == unpublish_sid) + { + output.audio_output_stream.take(); + } + if output + .screen_share_output_view + .as_ref() + .map_or(false, |(track, _)| track.sid() == unpublish_sid) + { + output.screen_share_output_view.take(); + } + cx.notify(); + } + + RoomEvent::TrackSubscribed { + publication, + participant, + track, + } => { + let output = self.remote_participant(participant); + match track { + RemoteTrack::Audio(track) => { + output.audio_output_stream = + Some((publication.clone(), play_remote_audio_track(&track, cx))); + } + RemoteTrack::Video(track) => { + output.screen_share_output_view = Some(( + track.clone(), + cx.new_view(|cx| RemoteVideoTrackView::new(track, cx)), + )); + } + } + cx.notify(); + } + + RoomEvent::TrackMuted { participant, .. } => { + if let Participant::Remote(participant) = participant { + self.remote_participant(participant).muted = true; + cx.notify(); + } + } + + RoomEvent::TrackUnmuted { participant, .. } => { + if let Participant::Remote(participant) = participant { + self.remote_participant(participant).muted = false; + cx.notify(); + } + } + + RoomEvent::ActiveSpeakersChanged { speakers } => { + for (identity, output) in &mut self.remote_participants { + output.speaking = speakers.iter().any(|speaker| { + if let Participant::Remote(speaker) = speaker { + speaker.identity() == *identity + } else { + false + } + }); + } + cx.notify(); + } + + _ => {} + } + + cx.notify(); + } + + fn remote_participant(&mut self, participant: RemoteParticipant) -> &mut ParticipantState { + match self + .remote_participants + .binary_search_by_key(&&participant.identity(), |row| &row.0) + { + Ok(ix) => &mut self.remote_participants[ix].1, + Err(ix) => { + self.remote_participants + .insert(ix, (participant.identity(), ParticipantState::default())); + &mut self.remote_participants[ix].1 + } + } + } + + fn toggle_mute(&mut self, cx: &mut ViewContext) { + if let Some(track) = &self.microphone_track { + if track.is_muted() { + track.unmute(); + } else { + track.mute(); + } + cx.notify(); + } else { + let participant = self.room.local_participant(); + cx.spawn(|this, mut cx| async move { + let (track, stream) = cx.update(|cx| capture_local_audio_track(cx))??; + let publication = participant + .publish_track( + LocalTrack::Audio(track), + TrackPublishOptions { + source: TrackSource::Microphone, + ..Default::default() + }, + ) + .await + .unwrap(); + this.update(&mut cx, |this, cx| { + this.microphone_track = Some(publication); + this.microphone_stream = Some(stream); + cx.notify(); + }) + }) + .detach(); + } + } + + fn toggle_screen_share(&mut self, cx: &mut ViewContext) { + if let Some(track) = self.screen_share_track.take() { + self.screen_share_stream.take(); + let participant = self.room.local_participant(); + cx.background_executor() + .spawn(async move { + participant.unpublish_track(&track.sid()).await.unwrap(); + }) + .detach(); + cx.notify(); + } else { + let participant = self.room.local_participant(); + let sources = cx.screen_capture_sources(); + cx.spawn(|this, mut cx| async move { + let sources = sources.await.unwrap()?; + let source = sources.into_iter().next().unwrap(); + let (track, stream) = capture_local_video_track(&*source).await?; + let publication = participant + .publish_track( + LocalTrack::Video(track), + TrackPublishOptions { + source: TrackSource::Screenshare, + video_codec: VideoCodec::H264, + ..Default::default() + }, + ) + .await + .unwrap(); + this.update(&mut cx, |this, cx| { + this.screen_share_track = Some(publication); + this.screen_share_stream = Some(stream); + cx.notify(); + }) + }) + .detach(); + } + } + + fn toggle_remote_audio_for_participant( + &mut self, + identity: &ParticipantIdentity, + cx: &mut ViewContext, + ) -> Option<()> { + let participant = self.remote_participants.iter().find_map(|(id, state)| { + if id == identity { + Some(state) + } else { + None + } + })?; + let publication = &participant.audio_output_stream.as_ref()?.0; + publication.set_enabled(!publication.is_enabled()); + cx.notify(); + Some(()) + } +} + +#[cfg(not(windows))] +impl Render for LivekitWindow { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + fn button() -> gpui::Div { + div() + .w(px(180.0)) + .h(px(30.0)) + .px_2() + .m_2() + .bg(rgb(0x8888ff)) + } + + div() + .bg(rgb(0xffffff)) + .size_full() + .flex() + .flex_col() + .child( + div().bg(rgb(0xffd4a8)).flex().flex_row().children([ + button() + .id("toggle-mute") + .child(if let Some(track) = &self.microphone_track { + if track.is_muted() { + "Unmute" + } else { + "Mute" + } + } else { + "Publish mic" + }) + .on_click(cx.listener(|this, _, cx| this.toggle_mute(cx))), + button() + .id("toggle-screen-share") + .child(if self.screen_share_track.is_none() { + "Share screen" + } else { + "Unshare screen" + }) + .on_click(cx.listener(|this, _, cx| this.toggle_screen_share(cx))), + ]), + ) + .child( + div() + .id("remote-participants") + .overflow_y_scroll() + .flex() + .flex_col() + .flex_grow() + .children(self.remote_participants.iter().map(|(identity, state)| { + div() + .h(px(300.0)) + .flex() + .flex_col() + .m_2() + .px_2() + .bg(rgb(0x8888ff)) + .child(SharedString::from(if state.speaking { + format!("{} (speaking)", &identity.0) + } else if state.muted { + format!("{} (muted)", &identity.0) + } else { + identity.0.clone() + })) + .when_some(state.audio_output_stream.as_ref(), |el, state| { + el.child( + button() + .id(SharedString::from(identity.0.clone())) + .child(if state.0.is_enabled() { + "Deafen" + } else { + "Undeafen" + }) + .on_click(cx.listener({ + let identity = identity.clone(); + move |this, _, cx| { + this.toggle_remote_audio_for_participant( + &identity, cx, + ); + } + })), + ) + }) + .children(state.screen_share_output_view.as_ref().map(|e| e.1.clone())) + })), + ) + } +} diff --git a/crates/live_kit_client/src/live_kit_client.rs b/crates/live_kit_client/src/live_kit_client.rs index 4820a4eedb..ad2e72d67f 100644 --- a/crates/live_kit_client/src/live_kit_client.rs +++ b/crates/live_kit_client/src/live_kit_client.rs @@ -1,37 +1,387 @@ -#![allow(clippy::arc_with_non_send_sync)] +#![cfg_attr(target_os = "windows", allow(unused))] -use std::sync::Arc; - -#[cfg(all(target_os = "macos", not(any(test, feature = "test-support"))))] -pub mod prod; - -#[cfg(all(target_os = "macos", not(any(test, feature = "test-support"))))] -pub use prod::*; - -#[cfg(any(test, feature = "test-support", not(target_os = "macos")))] +mod remote_video_track_view; +#[cfg(any(test, feature = "test-support", target_os = "windows"))] pub mod test; -#[cfg(any(test, feature = "test-support", not(target_os = "macos")))] +use anyhow::{anyhow, Context as _, Result}; +use cpal::{ + traits::{DeviceTrait, HostTrait, StreamTrait as _}, + StreamConfig, +}; +use futures::{io, Stream, StreamExt as _}; +use gpui::{AppContext, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, Task}; +use parking_lot::Mutex; +use std::{borrow::Cow, future::Future, pin::Pin, sync::Arc}; +use util::{debug_panic, ResultExt as _, TryFutureExt}; +#[cfg(not(target_os = "windows"))] +use webrtc::{ + audio_frame::AudioFrame, + audio_source::{native::NativeAudioSource, AudioSourceOptions, RtcAudioSource}, + audio_stream::native::NativeAudioStream, + video_frame::{VideoBuffer, VideoFrame, VideoRotation}, + video_source::{native::NativeVideoSource, RtcVideoSource, VideoResolution}, + video_stream::native::NativeVideoStream, +}; + +#[cfg(all(not(any(test, feature = "test-support")), not(target_os = "windows")))] +pub use livekit::*; +#[cfg(any(test, feature = "test-support", target_os = "windows"))] pub use test::*; -pub type Sid = String; +pub use remote_video_track_view::{RemoteVideoTrackView, RemoteVideoTrackViewEvent}; -#[derive(Clone, Eq, PartialEq)] -pub enum ConnectionState { - Disconnected, - Connected { url: String, token: String }, +pub struct AudioStream { + _tasks: [Task>; 2], } -#[derive(Clone)] -pub enum RoomUpdate { - ActiveSpeakersChanged { speakers: Vec }, - RemoteAudioTrackMuteChanged { track_id: Sid, muted: bool }, - SubscribedToRemoteVideoTrack(Arc), - SubscribedToRemoteAudioTrack(Arc, Arc), - UnsubscribedFromRemoteVideoTrack { publisher_id: Sid, track_id: Sid }, - UnsubscribedFromRemoteAudioTrack { publisher_id: Sid, track_id: Sid }, - LocalAudioTrackPublished { publication: LocalTrackPublication }, - LocalAudioTrackUnpublished { publication: LocalTrackPublication }, - LocalVideoTrackPublished { publication: LocalTrackPublication }, - LocalVideoTrackUnpublished { publication: LocalTrackPublication }, +struct Dispatcher(Arc); + +#[cfg(not(target_os = "windows"))] +impl livekit::dispatcher::Dispatcher for Dispatcher { + fn dispatch(&self, runnable: livekit::dispatcher::Runnable) { + self.0.dispatch(runnable, None); + } + + fn dispatch_after( + &self, + duration: std::time::Duration, + runnable: livekit::dispatcher::Runnable, + ) { + self.0.dispatch_after(duration, runnable); + } +} + +struct HttpClientAdapter(Arc); + +fn http_2_status(status: http_client::http::StatusCode) -> http_2::StatusCode { + http_2::StatusCode::from_u16(status.as_u16()) + .expect("valid status code to status code conversion") +} + +#[cfg(not(target_os = "windows"))] +impl livekit::dispatcher::HttpClient for HttpClientAdapter { + fn get( + &self, + url: &str, + ) -> Pin> + Send>> { + let http_client = self.0.clone(); + let url = url.to_string(); + Box::pin(async move { + let response = http_client + .get(&url, http_client::AsyncBody::empty(), false) + .await + .map_err(io::Error::other)?; + Ok(livekit::dispatcher::Response { + status: http_2_status(response.status()), + body: Box::pin(response.into_body()), + }) + }) + } + + fn send_async( + &self, + request: http_2::Request>, + ) -> Pin> + Send>> { + let http_client = self.0.clone(); + let mut builder = http_client::http::Request::builder() + .method(request.method().as_str()) + .uri(request.uri().to_string()); + + for (key, value) in request.headers().iter() { + builder = builder.header(key.as_str(), value.as_bytes()); + } + + if !request.extensions().is_empty() { + debug_panic!( + "Livekit sent an HTTP request with a protocol extension that Zed doesn't support!" + ); + } + + let request = builder + .body(http_client::AsyncBody::from_bytes( + request.into_body().into(), + )) + .unwrap(); + + Box::pin(async move { + let response = http_client.send(request).await.map_err(io::Error::other)?; + Ok(livekit::dispatcher::Response { + status: http_2_status(response.status()), + body: Box::pin(response.into_body()), + }) + }) + } +} + +#[cfg(target_os = "windows")] +pub fn init( + dispatcher: Arc, + http_client: Arc, +) { +} + +#[cfg(not(target_os = "windows"))] +pub fn init( + dispatcher: Arc, + http_client: Arc, +) { + livekit::dispatcher::set_dispatcher(Dispatcher(dispatcher)); + livekit::dispatcher::set_http_client(HttpClientAdapter(http_client)); +} + +#[cfg(not(target_os = "windows"))] +pub async fn capture_local_video_track( + capture_source: &dyn ScreenCaptureSource, +) -> Result<(track::LocalVideoTrack, Box)> { + let resolution = capture_source.resolution()?; + let track_source = NativeVideoSource::new(VideoResolution { + width: resolution.width.0 as u32, + height: resolution.height.0 as u32, + }); + + let capture_stream = capture_source + .stream({ + let track_source = track_source.clone(); + Box::new(move |frame| { + if let Some(buffer) = video_frame_buffer_to_webrtc(frame) { + track_source.capture_frame(&VideoFrame { + rotation: VideoRotation::VideoRotation0, + timestamp_us: 0, + buffer, + }); + } + }) + }) + .await??; + + Ok(( + track::LocalVideoTrack::create_video_track( + "screen share", + RtcVideoSource::Native(track_source), + ), + capture_stream, + )) +} + +#[cfg(not(target_os = "windows"))] +pub fn capture_local_audio_track( + cx: &mut AppContext, +) -> Result<(track::LocalAudioTrack, AudioStream)> { + let (frame_tx, mut frame_rx) = futures::channel::mpsc::unbounded(); + + let sample_rate; + let channels; + let stream; + if cfg!(any(test, feature = "test-support")) { + sample_rate = 1; + channels = 1; + stream = None; + } else { + let device = cpal::default_host() + .default_input_device() + .ok_or_else(|| anyhow!("no audio input device available"))?; + let config = device + .default_input_config() + .context("failed to get default input config")?; + sample_rate = config.sample_rate().0; + channels = config.channels() as u32; + stream = Some( + device + .build_input_stream_raw( + &config.config(), + cpal::SampleFormat::I16, + move |data, _: &_| { + frame_tx + .unbounded_send(AudioFrame { + data: Cow::Owned(data.as_slice::().unwrap().to_vec()), + sample_rate, + num_channels: channels, + samples_per_channel: data.len() as u32 / channels, + }) + .ok(); + }, + |err| log::error!("error capturing audio track: {:?}", err), + None, + ) + .context("failed to build input stream")?, + ); + } + + let source = NativeAudioSource::new( + AudioSourceOptions { + echo_cancellation: true, + noise_suppression: true, + auto_gain_control: false, + }, + sample_rate, + channels, + // TODO livekit: Pull these out of a proto later + 100, + ); + + let stream_task = cx.foreground_executor().spawn(async move { + if let Some(stream) = &stream { + stream.play().log_err(); + } + futures::future::pending().await + }); + + let transmit_task = cx.background_executor().spawn({ + let source = source.clone(); + async move { + while let Some(frame) = frame_rx.next().await { + source.capture_frame(&frame).await.ok(); + } + Some(()) + } + }); + + let track = + track::LocalAudioTrack::create_audio_track("microphone", RtcAudioSource::Native(source)); + + Ok(( + track, + AudioStream { + _tasks: [stream_task, transmit_task], + }, + )) +} + +#[cfg(not(target_os = "windows"))] +pub fn play_remote_audio_track( + track: &track::RemoteAudioTrack, + cx: &mut AppContext, +) -> AudioStream { + let buffer = Arc::new(Mutex::new(Vec::::new())); + let (stream_config_tx, mut stream_config_rx) = futures::channel::mpsc::unbounded(); + // TODO livekit: Pull these out of a proto later + let mut stream = NativeAudioStream::new(track.rtc_track(), 48000, 1); + + let receive_task = cx.background_executor().spawn({ + let buffer = buffer.clone(); + async move { + let mut stream_config = None; + while let Some(frame) = stream.next().await { + let mut buffer = buffer.lock(); + let buffer_size = frame.samples_per_channel * frame.num_channels; + debug_assert!(frame.data.len() == buffer_size as usize); + + let frame_config = StreamConfig { + channels: frame.num_channels as u16, + sample_rate: cpal::SampleRate(frame.sample_rate), + buffer_size: cpal::BufferSize::Fixed(buffer_size), + }; + + if stream_config.as_ref().map_or(true, |c| *c != frame_config) { + buffer.resize(buffer_size as usize, 0); + stream_config = Some(frame_config.clone()); + stream_config_tx.unbounded_send(frame_config).ok(); + } + + if frame.data.len() == buffer.len() { + buffer.copy_from_slice(&frame.data); + } else { + buffer.iter_mut().for_each(|x| *x = 0); + } + } + Some(()) + } + }); + + let play_task = cx.foreground_executor().spawn( + { + let buffer = buffer.clone(); + async move { + if cfg!(any(test, feature = "test-support")) { + return Err(anyhow!("can't play audio in tests")); + } + + let device = cpal::default_host() + .default_output_device() + .ok_or_else(|| anyhow!("no audio output device available"))?; + + let mut _output_stream = None; + while let Some(config) = stream_config_rx.next().await { + _output_stream = Some(device.build_output_stream( + &config, + { + let buffer = buffer.clone(); + move |data, _info| { + let buffer = buffer.lock(); + if data.len() == buffer.len() { + data.copy_from_slice(&buffer); + } else { + data.iter_mut().for_each(|x| *x = 0); + } + } + }, + |error| log::error!("error playing audio track: {:?}", error), + None, + )?); + } + + Ok(()) + } + } + .log_err(), + ); + + AudioStream { + _tasks: [receive_task, play_task], + } +} + +#[cfg(target_os = "windows")] +pub fn play_remote_video_track( + track: &track::RemoteVideoTrack, +) -> impl Stream { + futures::stream::empty() +} + +#[cfg(not(target_os = "windows"))] +pub fn play_remote_video_track( + track: &track::RemoteVideoTrack, +) -> impl Stream { + NativeVideoStream::new(track.rtc_track()) + .filter_map(|frame| async move { video_frame_buffer_from_webrtc(frame.buffer) }) +} + +#[cfg(target_os = "macos")] +fn video_frame_buffer_from_webrtc(buffer: Box) -> Option { + use core_foundation::base::TCFType as _; + use media::core_video::CVImageBuffer; + + let buffer = buffer.as_native()?; + let pixel_buffer = buffer.get_cv_pixel_buffer(); + if pixel_buffer.is_null() { + return None; + } + + unsafe { + Some(ScreenCaptureFrame(CVImageBuffer::wrap_under_get_rule( + pixel_buffer as _, + ))) + } +} + +#[cfg(not(any(target_os = "macos", target_os = "windows")))] +fn video_frame_buffer_from_webrtc(_buffer: Box) -> Option { + None +} + +#[cfg(target_os = "macos")] +fn video_frame_buffer_to_webrtc(frame: ScreenCaptureFrame) -> Option> { + use core_foundation::base::TCFType as _; + + let pixel_buffer = frame.0.as_concrete_TypeRef(); + std::mem::forget(frame.0); + unsafe { + Some(webrtc::video_frame::native::NativeBuffer::from_cv_pixel_buffer(pixel_buffer as _)) + } +} + +#[cfg(not(any(target_os = "macos", target_os = "windows")))] +fn video_frame_buffer_to_webrtc(_frame: ScreenCaptureFrame) -> Option> { + None as Option> } diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs deleted file mode 100644 index f7452da710..0000000000 --- a/crates/live_kit_client/src/prod.rs +++ /dev/null @@ -1,981 +0,0 @@ -use crate::{ConnectionState, RoomUpdate, Sid}; -use anyhow::{anyhow, Context, Result}; -use core_foundation::{ - array::{CFArray, CFArrayRef}, - base::{CFRelease, CFRetain, TCFType}, - string::{CFString, CFStringRef}, -}; -use futures::{ - channel::{mpsc, oneshot}, - Future, -}; -pub use media::core_video::CVImageBuffer; -use media::core_video::CVImageBufferRef; -use parking_lot::Mutex; -use postage::watch; -use std::{ - ffi::c_void, - sync::{Arc, Weak}, -}; - -macro_rules! pointer_type { - ($pointer_name:ident) => { - #[repr(transparent)] - #[derive(Copy, Clone, Debug)] - pub struct $pointer_name(pub *const std::ffi::c_void); - unsafe impl Send for $pointer_name {} - }; -} - -mod swift { - pointer_type!(Room); - pointer_type!(LocalAudioTrack); - pointer_type!(RemoteAudioTrack); - pointer_type!(LocalVideoTrack); - pointer_type!(RemoteVideoTrack); - pointer_type!(LocalTrackPublication); - pointer_type!(RemoteTrackPublication); - pointer_type!(MacOSDisplay); - pointer_type!(RoomDelegate); -} - -extern "C" { - fn LKRoomDelegateCreate( - callback_data: *mut c_void, - on_did_disconnect: extern "C" fn(callback_data: *mut c_void), - on_did_subscribe_to_remote_audio_track: extern "C" fn( - callback_data: *mut c_void, - publisher_id: CFStringRef, - track_id: CFStringRef, - remote_track: swift::RemoteAudioTrack, - remote_publication: swift::RemoteTrackPublication, - ), - on_did_unsubscribe_from_remote_audio_track: extern "C" fn( - callback_data: *mut c_void, - publisher_id: CFStringRef, - track_id: CFStringRef, - ), - on_mute_changed_from_remote_audio_track: extern "C" fn( - callback_data: *mut c_void, - track_id: CFStringRef, - muted: bool, - ), - on_active_speakers_changed: extern "C" fn( - callback_data: *mut c_void, - participants: CFArrayRef, - ), - on_did_subscribe_to_remote_video_track: extern "C" fn( - callback_data: *mut c_void, - publisher_id: CFStringRef, - track_id: CFStringRef, - remote_track: swift::RemoteVideoTrack, - ), - on_did_unsubscribe_from_remote_video_track: extern "C" fn( - callback_data: *mut c_void, - publisher_id: CFStringRef, - track_id: CFStringRef, - ), - on_did_publish_or_unpublish_local_audio_track: extern "C" fn( - callback_data: *mut c_void, - publication: swift::LocalTrackPublication, - is_published: bool, - ), - on_did_publish_or_unpublish_local_video_track: extern "C" fn( - callback_data: *mut c_void, - publication: swift::LocalTrackPublication, - is_published: bool, - ), - ) -> swift::RoomDelegate; - - fn LKRoomCreate(delegate: swift::RoomDelegate) -> swift::Room; - fn LKRoomConnect( - room: swift::Room, - url: CFStringRef, - token: CFStringRef, - callback: extern "C" fn(*mut c_void, CFStringRef), - callback_data: *mut c_void, - ); - fn LKRoomDisconnect(room: swift::Room); - fn LKRoomPublishVideoTrack( - room: swift::Room, - track: swift::LocalVideoTrack, - callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef), - callback_data: *mut c_void, - ); - fn LKRoomPublishAudioTrack( - room: swift::Room, - track: swift::LocalAudioTrack, - callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef), - callback_data: *mut c_void, - ); - fn LKRoomUnpublishTrack(room: swift::Room, publication: swift::LocalTrackPublication); - - fn LKRoomAudioTracksForRemoteParticipant( - room: swift::Room, - participant_id: CFStringRef, - ) -> CFArrayRef; - - fn LKRoomAudioTrackPublicationsForRemoteParticipant( - room: swift::Room, - participant_id: CFStringRef, - ) -> CFArrayRef; - - fn LKRoomVideoTracksForRemoteParticipant( - room: swift::Room, - participant_id: CFStringRef, - ) -> CFArrayRef; - - fn LKVideoRendererCreate( - callback_data: *mut c_void, - on_frame: extern "C" fn(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool, - on_drop: extern "C" fn(callback_data: *mut c_void), - ) -> *const c_void; - - fn LKRemoteAudioTrackGetSid(track: swift::RemoteAudioTrack) -> CFStringRef; - fn LKRemoteVideoTrackGetSid(track: swift::RemoteVideoTrack) -> CFStringRef; - fn LKRemoteAudioTrackStart(track: swift::RemoteAudioTrack); - fn LKRemoteAudioTrackStop(track: swift::RemoteAudioTrack); - fn LKVideoTrackAddRenderer(track: swift::RemoteVideoTrack, renderer: *const c_void); - - fn LKDisplaySources( - callback_data: *mut c_void, - callback: extern "C" fn( - callback_data: *mut c_void, - sources: CFArrayRef, - error: CFStringRef, - ), - ); - fn LKCreateScreenShareTrackForDisplay(display: swift::MacOSDisplay) -> swift::LocalVideoTrack; - fn LKLocalAudioTrackCreateTrack() -> swift::LocalAudioTrack; - - fn LKLocalTrackPublicationSetMute( - publication: swift::LocalTrackPublication, - muted: bool, - on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef), - callback_data: *mut c_void, - ); - - fn LKRemoteTrackPublicationSetEnabled( - publication: swift::RemoteTrackPublication, - enabled: bool, - on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef), - callback_data: *mut c_void, - ); - - fn LKLocalTrackPublicationIsMuted(publication: swift::LocalTrackPublication) -> bool; - fn LKRemoteTrackPublicationIsMuted(publication: swift::RemoteTrackPublication) -> bool; - fn LKLocalTrackPublicationGetSid(publication: swift::LocalTrackPublication) -> CFStringRef; - fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef; -} - -pub struct Room { - native_room: swift::Room, - connection: Mutex<( - watch::Sender, - watch::Receiver, - )>, - update_subscribers: Mutex>>, - _delegate: RoomDelegate, -} - -impl Room { - pub fn new() -> Arc { - Arc::new_cyclic(|weak_room| { - let delegate = RoomDelegate::new(weak_room.clone()); - Self { - native_room: unsafe { LKRoomCreate(delegate.native_delegate) }, - connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)), - update_subscribers: Default::default(), - _delegate: delegate, - } - }) - } - - pub fn status(&self) -> watch::Receiver { - self.connection.lock().1.clone() - } - - pub fn connect(self: &Arc, url: &str, token: &str) -> impl Future> { - let url = CFString::new(url); - let token = CFString::new(token); - let (did_connect, tx, rx) = Self::build_done_callback(); - unsafe { - LKRoomConnect( - self.native_room, - url.as_concrete_TypeRef(), - token.as_concrete_TypeRef(), - did_connect, - tx, - ) - } - - let this = self.clone(); - let url = url.to_string(); - let token = token.to_string(); - async move { - rx.await.unwrap().context("error connecting to room")?; - *this.connection.lock().0.borrow_mut() = ConnectionState::Connected { url, token }; - Ok(()) - } - } - - fn did_disconnect(&self) { - *self.connection.lock().0.borrow_mut() = ConnectionState::Disconnected; - } - - pub fn display_sources(self: &Arc) -> impl Future>> { - extern "C" fn callback(tx: *mut c_void, sources: CFArrayRef, error: CFStringRef) { - unsafe { - let tx = Box::from_raw(tx as *mut oneshot::Sender>>); - - if sources.is_null() { - let _ = tx.send(Err(anyhow!("{}", CFString::wrap_under_get_rule(error)))); - } else { - let sources = CFArray::wrap_under_get_rule(sources) - .into_iter() - .map(|source| MacOSDisplay::new(swift::MacOSDisplay(*source))) - .collect(); - - let _ = tx.send(Ok(sources)); - } - } - } - - let (tx, rx) = oneshot::channel(); - - unsafe { - LKDisplaySources(Box::into_raw(Box::new(tx)) as *mut _, callback); - } - - async move { rx.await.unwrap() } - } - - pub fn publish_video_track( - self: &Arc, - track: LocalVideoTrack, - ) -> impl Future> { - let (tx, rx) = oneshot::channel::>(); - extern "C" fn callback( - tx: *mut c_void, - publication: swift::LocalTrackPublication, - error: CFStringRef, - ) { - let tx = - unsafe { Box::from_raw(tx as *mut oneshot::Sender>) }; - if error.is_null() { - let _ = tx.send(Ok(LocalTrackPublication::new(publication))); - } else { - let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; - let _ = tx.send(Err(anyhow!(error))); - } - } - unsafe { - LKRoomPublishVideoTrack( - self.native_room, - track.0, - callback, - Box::into_raw(Box::new(tx)) as *mut c_void, - ); - } - async { rx.await.unwrap().context("error publishing video track") } - } - - pub fn publish_audio_track( - self: &Arc, - track: LocalAudioTrack, - ) -> impl Future> { - let (tx, rx) = oneshot::channel::>(); - extern "C" fn callback( - tx: *mut c_void, - publication: swift::LocalTrackPublication, - error: CFStringRef, - ) { - let tx = - unsafe { Box::from_raw(tx as *mut oneshot::Sender>) }; - if error.is_null() { - let _ = tx.send(Ok(LocalTrackPublication::new(publication))); - } else { - let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; - let _ = tx.send(Err(anyhow!(error))); - } - } - unsafe { - LKRoomPublishAudioTrack( - self.native_room, - track.0, - callback, - Box::into_raw(Box::new(tx)) as *mut c_void, - ); - } - async { rx.await.unwrap().context("error publishing audio track") } - } - - pub fn unpublish_track(&self, publication: LocalTrackPublication) { - unsafe { - LKRoomUnpublishTrack(self.native_room, publication.0); - } - } - - pub fn remote_video_tracks(&self, participant_id: &str) -> Vec> { - unsafe { - let tracks = LKRoomVideoTracksForRemoteParticipant( - self.native_room, - CFString::new(participant_id).as_concrete_TypeRef(), - ); - - if tracks.is_null() { - Vec::new() - } else { - let tracks = CFArray::wrap_under_get_rule(tracks); - tracks - .into_iter() - .map(|native_track| { - let native_track = swift::RemoteVideoTrack(*native_track); - let id = - CFString::wrap_under_get_rule(LKRemoteVideoTrackGetSid(native_track)) - .to_string(); - Arc::new(RemoteVideoTrack::new( - native_track, - id, - participant_id.into(), - )) - }) - .collect() - } - } - } - - pub fn remote_audio_tracks(&self, participant_id: &str) -> Vec> { - unsafe { - let tracks = LKRoomAudioTracksForRemoteParticipant( - self.native_room, - CFString::new(participant_id).as_concrete_TypeRef(), - ); - - if tracks.is_null() { - Vec::new() - } else { - let tracks = CFArray::wrap_under_get_rule(tracks); - tracks - .into_iter() - .map(|native_track| { - let native_track = swift::RemoteAudioTrack(*native_track); - let id = - CFString::wrap_under_get_rule(LKRemoteAudioTrackGetSid(native_track)) - .to_string(); - Arc::new(RemoteAudioTrack::new( - native_track, - id, - participant_id.into(), - )) - }) - .collect() - } - } - } - - pub fn remote_audio_track_publications( - &self, - participant_id: &str, - ) -> Vec> { - unsafe { - let tracks = LKRoomAudioTrackPublicationsForRemoteParticipant( - self.native_room, - CFString::new(participant_id).as_concrete_TypeRef(), - ); - - if tracks.is_null() { - Vec::new() - } else { - let tracks = CFArray::wrap_under_get_rule(tracks); - tracks - .into_iter() - .map(|native_track_publication| { - let native_track_publication = - swift::RemoteTrackPublication(*native_track_publication); - Arc::new(RemoteTrackPublication::new(native_track_publication)) - }) - .collect() - } - } - } - - pub fn updates(&self) -> mpsc::UnboundedReceiver { - let (tx, rx) = mpsc::unbounded(); - self.update_subscribers.lock().push(tx); - rx - } - - fn did_subscribe_to_remote_audio_track( - &self, - track: RemoteAudioTrack, - publication: RemoteTrackPublication, - ) { - let track = Arc::new(track); - let publication = Arc::new(publication); - self.update_subscribers.lock().retain(|tx| { - tx.unbounded_send(RoomUpdate::SubscribedToRemoteAudioTrack( - track.clone(), - publication.clone(), - )) - .is_ok() - }); - } - - fn did_unsubscribe_from_remote_audio_track(&self, publisher_id: String, track_id: String) { - self.update_subscribers.lock().retain(|tx| { - tx.unbounded_send(RoomUpdate::UnsubscribedFromRemoteAudioTrack { - publisher_id: publisher_id.clone(), - track_id: track_id.clone(), - }) - .is_ok() - }); - } - - fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) { - self.update_subscribers.lock().retain(|tx| { - tx.unbounded_send(RoomUpdate::RemoteAudioTrackMuteChanged { - track_id: track_id.clone(), - muted, - }) - .is_ok() - }); - } - - fn active_speakers_changed(&self, speakers: Vec) { - self.update_subscribers.lock().retain(move |tx| { - tx.unbounded_send(RoomUpdate::ActiveSpeakersChanged { - speakers: speakers.clone(), - }) - .is_ok() - }); - } - - fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) { - let track = Arc::new(track); - self.update_subscribers.lock().retain(|tx| { - tx.unbounded_send(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) - .is_ok() - }); - } - - fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) { - self.update_subscribers.lock().retain(|tx| { - tx.unbounded_send(RoomUpdate::UnsubscribedFromRemoteVideoTrack { - publisher_id: publisher_id.clone(), - track_id: track_id.clone(), - }) - .is_ok() - }); - } - - fn build_done_callback() -> ( - extern "C" fn(*mut c_void, CFStringRef), - *mut c_void, - oneshot::Receiver>, - ) { - let (tx, rx) = oneshot::channel(); - extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) { - let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender>) }; - if error.is_null() { - let _ = tx.send(Ok(())); - } else { - let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; - let _ = tx.send(Err(anyhow!(error))); - } - } - ( - done_callback, - Box::into_raw(Box::new(tx)) as *mut c_void, - rx, - ) - } - - pub fn set_display_sources(&self, _: Vec) { - unreachable!("This is a test-only function") - } -} - -impl Drop for Room { - fn drop(&mut self) { - unsafe { - LKRoomDisconnect(self.native_room); - CFRelease(self.native_room.0); - } - } -} - -struct RoomDelegate { - native_delegate: swift::RoomDelegate, - weak_room: *mut c_void, -} - -impl RoomDelegate { - fn new(weak_room: Weak) -> Self { - let weak_room = weak_room.into_raw() as *mut c_void; - let native_delegate = unsafe { - LKRoomDelegateCreate( - weak_room, - Self::on_did_disconnect, - Self::on_did_subscribe_to_remote_audio_track, - Self::on_did_unsubscribe_from_remote_audio_track, - Self::on_mute_change_from_remote_audio_track, - Self::on_active_speakers_changed, - Self::on_did_subscribe_to_remote_video_track, - Self::on_did_unsubscribe_from_remote_video_track, - Self::on_did_publish_or_unpublish_local_audio_track, - Self::on_did_publish_or_unpublish_local_video_track, - ) - }; - Self { - native_delegate, - weak_room, - } - } - - extern "C" fn on_did_disconnect(room: *mut c_void) { - let room = unsafe { Weak::from_raw(room as *mut Room) }; - if let Some(room) = room.upgrade() { - room.did_disconnect(); - } - let _ = Weak::into_raw(room); - } - - extern "C" fn on_did_subscribe_to_remote_audio_track( - room: *mut c_void, - publisher_id: CFStringRef, - track_id: CFStringRef, - track: swift::RemoteAudioTrack, - publication: swift::RemoteTrackPublication, - ) { - let room = unsafe { Weak::from_raw(room as *mut Room) }; - let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; - let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; - let track = RemoteAudioTrack::new(track, track_id, publisher_id); - let publication = RemoteTrackPublication::new(publication); - if let Some(room) = room.upgrade() { - room.did_subscribe_to_remote_audio_track(track, publication); - } - let _ = Weak::into_raw(room); - } - - extern "C" fn on_did_unsubscribe_from_remote_audio_track( - room: *mut c_void, - publisher_id: CFStringRef, - track_id: CFStringRef, - ) { - let room = unsafe { Weak::from_raw(room as *mut Room) }; - let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; - let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; - if let Some(room) = room.upgrade() { - room.did_unsubscribe_from_remote_audio_track(publisher_id, track_id); - } - let _ = Weak::into_raw(room); - } - - extern "C" fn on_mute_change_from_remote_audio_track( - room: *mut c_void, - track_id: CFStringRef, - muted: bool, - ) { - let room = unsafe { Weak::from_raw(room as *mut Room) }; - let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; - if let Some(room) = room.upgrade() { - room.mute_changed_from_remote_audio_track(track_id, muted); - } - let _ = Weak::into_raw(room); - } - - extern "C" fn on_active_speakers_changed(room: *mut c_void, participants: CFArrayRef) { - if participants.is_null() { - return; - } - - let room = unsafe { Weak::from_raw(room as *mut Room) }; - let speakers = unsafe { - CFArray::wrap_under_get_rule(participants) - .into_iter() - .map( - |speaker: core_foundation::base::ItemRef<'_, *const c_void>| { - CFString::wrap_under_get_rule(*speaker as CFStringRef).to_string() - }, - ) - .collect() - }; - - if let Some(room) = room.upgrade() { - room.active_speakers_changed(speakers); - } - let _ = Weak::into_raw(room); - } - - extern "C" fn on_did_subscribe_to_remote_video_track( - room: *mut c_void, - publisher_id: CFStringRef, - track_id: CFStringRef, - track: swift::RemoteVideoTrack, - ) { - let room = unsafe { Weak::from_raw(room as *mut Room) }; - let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; - let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; - let track = RemoteVideoTrack::new(track, track_id, publisher_id); - if let Some(room) = room.upgrade() { - room.did_subscribe_to_remote_video_track(track); - } - let _ = Weak::into_raw(room); - } - - extern "C" fn on_did_unsubscribe_from_remote_video_track( - room: *mut c_void, - publisher_id: CFStringRef, - track_id: CFStringRef, - ) { - let room = unsafe { Weak::from_raw(room as *mut Room) }; - let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; - let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; - if let Some(room) = room.upgrade() { - room.did_unsubscribe_from_remote_video_track(publisher_id, track_id); - } - let _ = Weak::into_raw(room); - } - - extern "C" fn on_did_publish_or_unpublish_local_audio_track( - room: *mut c_void, - publication: swift::LocalTrackPublication, - is_published: bool, - ) { - let room = unsafe { Weak::from_raw(room as *mut Room) }; - if let Some(room) = room.upgrade() { - let publication = LocalTrackPublication::new(publication); - let update = if is_published { - RoomUpdate::LocalAudioTrackPublished { publication } - } else { - RoomUpdate::LocalAudioTrackUnpublished { publication } - }; - room.update_subscribers - .lock() - .retain(|tx| tx.unbounded_send(update.clone()).is_ok()); - } - let _ = Weak::into_raw(room); - } - - extern "C" fn on_did_publish_or_unpublish_local_video_track( - room: *mut c_void, - publication: swift::LocalTrackPublication, - is_published: bool, - ) { - let room = unsafe { Weak::from_raw(room as *mut Room) }; - if let Some(room) = room.upgrade() { - let publication = LocalTrackPublication::new(publication); - let update = if is_published { - RoomUpdate::LocalVideoTrackPublished { publication } - } else { - RoomUpdate::LocalVideoTrackUnpublished { publication } - }; - room.update_subscribers - .lock() - .retain(|tx| tx.unbounded_send(update.clone()).is_ok()); - } - let _ = Weak::into_raw(room); - } -} - -impl Drop for RoomDelegate { - fn drop(&mut self) { - unsafe { - CFRelease(self.native_delegate.0); - let _ = Weak::from_raw(self.weak_room as *mut Room); - } - } -} - -pub struct LocalAudioTrack(swift::LocalAudioTrack); - -impl LocalAudioTrack { - pub fn create() -> Self { - Self(unsafe { LKLocalAudioTrackCreateTrack() }) - } -} - -impl Drop for LocalAudioTrack { - fn drop(&mut self) { - unsafe { CFRelease(self.0 .0) } - } -} - -pub struct LocalVideoTrack(swift::LocalVideoTrack); - -impl LocalVideoTrack { - pub fn screen_share_for_display(display: &MacOSDisplay) -> Self { - Self(unsafe { LKCreateScreenShareTrackForDisplay(display.0) }) - } -} - -impl Drop for LocalVideoTrack { - fn drop(&mut self) { - unsafe { CFRelease(self.0 .0) } - } -} - -pub struct LocalTrackPublication(swift::LocalTrackPublication); - -impl LocalTrackPublication { - pub fn new(native_track_publication: swift::LocalTrackPublication) -> Self { - unsafe { - CFRetain(native_track_publication.0); - } - Self(native_track_publication) - } - - pub fn sid(&self) -> String { - unsafe { CFString::wrap_under_get_rule(LKLocalTrackPublicationGetSid(self.0)).to_string() } - } - - pub fn set_mute(&self, muted: bool) -> impl Future> { - let (tx, rx) = futures::channel::oneshot::channel(); - - extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) { - let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender>) }; - if error.is_null() { - tx.send(Ok(())).ok(); - } else { - let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; - tx.send(Err(anyhow!(error))).ok(); - } - } - - unsafe { - LKLocalTrackPublicationSetMute( - self.0, - muted, - complete_callback, - Box::into_raw(Box::new(tx)) as *mut c_void, - ) - } - - async move { rx.await.unwrap() } - } - - pub fn is_muted(&self) -> bool { - unsafe { LKLocalTrackPublicationIsMuted(self.0) } - } -} - -impl Clone for LocalTrackPublication { - fn clone(&self) -> Self { - unsafe { - CFRetain(self.0 .0); - } - Self(self.0) - } -} - -impl Drop for LocalTrackPublication { - fn drop(&mut self) { - unsafe { CFRelease(self.0 .0) } - } -} - -pub struct RemoteTrackPublication(swift::RemoteTrackPublication); - -impl RemoteTrackPublication { - pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self { - unsafe { - CFRetain(native_track_publication.0); - } - Self(native_track_publication) - } - - pub fn sid(&self) -> String { - unsafe { CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.0)).to_string() } - } - - pub fn is_muted(&self) -> bool { - unsafe { LKRemoteTrackPublicationIsMuted(self.0) } - } - - pub fn set_enabled(&self, enabled: bool) -> impl Future> { - let (tx, rx) = futures::channel::oneshot::channel(); - - extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) { - let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender>) }; - if error.is_null() { - tx.send(Ok(())).ok(); - } else { - let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; - tx.send(Err(anyhow!(error))).ok(); - } - } - - unsafe { - LKRemoteTrackPublicationSetEnabled( - self.0, - enabled, - complete_callback, - Box::into_raw(Box::new(tx)) as *mut c_void, - ) - } - - async move { rx.await.unwrap() } - } -} - -impl Drop for RemoteTrackPublication { - fn drop(&mut self) { - unsafe { CFRelease(self.0 .0) } - } -} - -#[derive(Debug)] -pub struct RemoteAudioTrack { - native_track: swift::RemoteAudioTrack, - sid: Sid, - publisher_id: String, -} - -impl RemoteAudioTrack { - fn new(native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String) -> Self { - unsafe { - CFRetain(native_track.0); - } - Self { - native_track, - sid, - publisher_id, - } - } - - pub fn sid(&self) -> &str { - &self.sid - } - - pub fn publisher_id(&self) -> &str { - &self.publisher_id - } - - pub fn start(&self) { - unsafe { LKRemoteAudioTrackStart(self.native_track) } - } - - pub fn stop(&self) { - unsafe { LKRemoteAudioTrackStop(self.native_track) } - } -} - -impl Drop for RemoteAudioTrack { - fn drop(&mut self) { - // todo: uncomment this `CFRelease`, unless we find that it was causing - // the crash in the `livekit.multicast` thread. - // - // unsafe { CFRelease(self.native_track.0) } - let _ = self.native_track; - } -} - -#[derive(Debug)] -pub struct RemoteVideoTrack { - native_track: swift::RemoteVideoTrack, - sid: Sid, - publisher_id: String, -} - -impl RemoteVideoTrack { - fn new(native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String) -> Self { - unsafe { - CFRetain(native_track.0); - } - Self { - native_track, - sid, - publisher_id, - } - } - - pub fn sid(&self) -> &str { - &self.sid - } - - pub fn publisher_id(&self) -> &str { - &self.publisher_id - } - - pub fn frames(&self) -> async_broadcast::Receiver { - extern "C" fn on_frame(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool { - unsafe { - let tx = Box::from_raw(callback_data as *mut async_broadcast::Sender); - let buffer = CVImageBuffer::wrap_under_get_rule(frame); - let result = tx.try_broadcast(Frame(buffer)); - let _ = Box::into_raw(tx); - match result { - Ok(_) => true, - Err(async_broadcast::TrySendError::Closed(_)) - | Err(async_broadcast::TrySendError::Inactive(_)) => { - log::warn!("no active receiver for frame"); - false - } - Err(async_broadcast::TrySendError::Full(_)) => { - log::warn!("skipping frame as receiver is not keeping up"); - true - } - } - } - } - - extern "C" fn on_drop(callback_data: *mut c_void) { - unsafe { - let _ = Box::from_raw(callback_data as *mut async_broadcast::Sender); - } - } - - let (tx, rx) = async_broadcast::broadcast(64); - unsafe { - let renderer = LKVideoRendererCreate( - Box::into_raw(Box::new(tx)) as *mut c_void, - on_frame, - on_drop, - ); - LKVideoTrackAddRenderer(self.native_track, renderer); - rx - } - } -} - -impl Drop for RemoteVideoTrack { - fn drop(&mut self) { - unsafe { CFRelease(self.native_track.0) } - } -} - -pub struct MacOSDisplay(swift::MacOSDisplay); - -impl MacOSDisplay { - fn new(ptr: swift::MacOSDisplay) -> Self { - unsafe { - CFRetain(ptr.0); - } - Self(ptr) - } -} - -impl Drop for MacOSDisplay { - fn drop(&mut self) { - unsafe { CFRelease(self.0 .0) } - } -} - -#[derive(Clone)] -pub struct Frame(CVImageBuffer); - -impl Frame { - pub fn width(&self) -> usize { - self.0.width() - } - - pub fn height(&self) -> usize { - self.0.height() - } - - pub fn image(&self) -> CVImageBuffer { - self.0.clone() - } -} diff --git a/crates/live_kit_client/src/remote_video_track_view.rs b/crates/live_kit_client/src/remote_video_track_view.rs new file mode 100644 index 0000000000..bbfaea1875 --- /dev/null +++ b/crates/live_kit_client/src/remote_video_track_view.rs @@ -0,0 +1,61 @@ +use crate::track::RemoteVideoTrack; +use anyhow::Result; +use futures::StreamExt as _; +use gpui::{ + Empty, EventEmitter, IntoElement, Render, ScreenCaptureFrame, Task, View, ViewContext, + VisualContext as _, +}; + +pub struct RemoteVideoTrackView { + track: RemoteVideoTrack, + frame: Option, + _maintain_frame: Task>, +} + +#[derive(Debug)] +pub enum RemoteVideoTrackViewEvent { + Close, +} + +impl RemoteVideoTrackView { + pub fn new(track: RemoteVideoTrack, cx: &mut ViewContext) -> Self { + cx.focus_handle(); + let frames = super::play_remote_video_track(&track); + + Self { + track, + frame: None, + _maintain_frame: cx.spawn(|this, mut cx| async move { + futures::pin_mut!(frames); + while let Some(frame) = frames.next().await { + this.update(&mut cx, |this, cx| { + this.frame = Some(frame); + cx.notify(); + })?; + } + this.update(&mut cx, |_, cx| cx.emit(RemoteVideoTrackViewEvent::Close))?; + Ok(()) + }), + } + } + + pub fn clone(&self, cx: &mut ViewContext) -> View { + cx.new_view(|cx| Self::new(self.track.clone(), cx)) + } +} + +impl EventEmitter for RemoteVideoTrackView {} + +impl Render for RemoteVideoTrackView { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + #[cfg(target_os = "macos")] + if let Some(frame) = &self.frame { + use gpui::Styled as _; + return gpui::surface(frame.0.clone()) + .size_full() + .into_any_element(); + } + + Empty.into_any_element() + } +} diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 2c26c88f72..1bd2e60d17 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -1,32 +1,42 @@ -use crate::{ConnectionState, RoomUpdate, Sid}; +pub mod participant; +pub mod publication; +pub mod track; + +#[cfg(not(windows))] +pub mod webrtc; + +#[cfg(not(windows))] +use self::id::*; +use self::{participant::*, publication::*, track::*}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use collections::{btree_map::Entry as BTreeEntry, hash_map::Entry, BTreeMap, HashMap, HashSet}; -use futures::Stream; -use gpui::{BackgroundExecutor, SurfaceSource}; +use gpui::BackgroundExecutor; use live_kit_server::{proto, token}; - +#[cfg(not(windows))] +use livekit::options::TrackPublishOptions; use parking_lot::Mutex; -use postage::watch; -use std::{ - future::Future, - mem, - sync::{ - atomic::{AtomicBool, Ordering::SeqCst}, - Arc, Weak, - }, +use postage::{mpsc, sink::Sink}; +use std::sync::{ + atomic::{AtomicBool, Ordering::SeqCst}, + Arc, Weak, }; +#[cfg(not(windows))] +pub use livekit::{id, options, ConnectionState, DisconnectReason, RoomOptions}; + static SERVERS: Mutex>> = Mutex::new(BTreeMap::new()); pub struct TestServer { pub url: String, pub api_key: String, pub secret_key: String, + #[cfg(not(target_os = "windows"))] rooms: Mutex>, executor: BackgroundExecutor, } +#[cfg(not(target_os = "windows"))] impl TestServer { pub fn create( url: String, @@ -73,9 +83,8 @@ impl TestServer { } pub async fn create_room(&self, room: String) -> Result<()> { - // todo(linux): Remove this once the cross-platform LiveKit implementation is merged - #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); if let Entry::Vacant(e) = server_rooms.entry(room.clone()) { e.insert(Default::default()); @@ -86,10 +95,8 @@ impl TestServer { } async fn delete_room(&self, room: String) -> Result<()> { - // TODO: clear state associated with all `Room`s. - // todo(linux): Remove this once the cross-platform LiveKit implementation is merged - #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); server_rooms .remove(&room) @@ -97,46 +104,64 @@ impl TestServer { Ok(()) } - async fn join_room(&self, token: String, client_room: Arc) -> Result<()> { - // todo(linux): Remove this once the cross-platform LiveKit implementation is merged - #[cfg(any(test, feature = "test-support"))] + async fn join_room(&self, token: String, client_room: Room) -> Result { self.executor.simulate_random_delay().await; let claims = live_kit_server::token::validate(&token, &self.secret_key)?; - let identity = claims.sub.unwrap().to_string(); + let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); let room = (*server_rooms).entry(room_name.to_string()).or_default(); if let Entry::Vacant(e) = room.client_rooms.entry(identity.clone()) { - for track in &room.video_tracks { + for server_track in &room.video_tracks { + let track = RemoteTrack::Video(RemoteVideoTrack { + server_track: server_track.clone(), + _room: client_room.downgrade(), + }); client_room .0 .lock() .updates_tx - .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(Arc::new( - RemoteVideoTrack { - server_track: track.clone(), + .blocking_send(RoomEvent::TrackSubscribed { + track: track.clone(), + publication: RemoteTrackPublication { + sid: server_track.sid.clone(), + room: client_room.downgrade(), + track, }, - ))) + participant: RemoteParticipant { + room: client_room.downgrade(), + identity: server_track.publisher_id.clone(), + }, + }) .unwrap(); } - for track in &room.audio_tracks { + for server_track in &room.audio_tracks { + let track = RemoteTrack::Audio(RemoteAudioTrack { + server_track: server_track.clone(), + room: client_room.downgrade(), + }); client_room .0 .lock() .updates_tx - .try_broadcast(RoomUpdate::SubscribedToRemoteAudioTrack( - Arc::new(RemoteAudioTrack { - server_track: track.clone(), - room: Arc::downgrade(&client_room), - }), - Arc::new(RemoteTrackPublication), - )) + .blocking_send(RoomEvent::TrackSubscribed { + track: track.clone(), + publication: RemoteTrackPublication { + sid: server_track.sid.clone(), + room: client_room.downgrade(), + track, + }, + participant: RemoteParticipant { + room: client_room.downgrade(), + identity: server_track.publisher_id.clone(), + }, + }) .unwrap(); } e.insert(client_room); - Ok(()) + Ok(identity) } else { Err(anyhow!( "{:?} attempted to join room {:?} twice", @@ -147,11 +172,10 @@ impl TestServer { } async fn leave_room(&self, token: String) -> Result<()> { - // todo(linux): Remove this once the cross-platform LiveKit implementation is merged - #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; - let identity = claims.sub.unwrap().to_string(); + let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); let room = server_rooms @@ -167,10 +191,44 @@ impl TestServer { Ok(()) } - async fn remove_participant(&self, room_name: String, identity: String) -> Result<()> { - // TODO: clear state associated with the `Room`. - // todo(linux): Remove this once the cross-platform LiveKit implementation is merged - #[cfg(any(test, feature = "test-support"))] + fn remote_participants( + &self, + token: String, + ) -> Result> { + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let local_identity = ParticipantIdentity(claims.sub.unwrap().to_string()); + let room_name = claims.video.room.unwrap().to_string(); + + if let Some(server_room) = self.rooms.lock().get(&room_name) { + let room = server_room + .client_rooms + .get(&local_identity) + .unwrap() + .downgrade(); + Ok(server_room + .client_rooms + .iter() + .filter(|(identity, _)| *identity != &local_identity) + .map(|(identity, _)| { + ( + identity.clone(), + RemoteParticipant { + room: room.clone(), + identity: identity.clone(), + }, + ) + }) + .collect()) + } else { + Ok(Default::default()) + } + } + + async fn remove_participant( + &self, + room_name: String, + identity: ParticipantIdentity, + ) -> Result<()> { self.executor.simulate_random_delay().await; let mut server_rooms = self.rooms.lock(); @@ -193,25 +251,32 @@ impl TestServer { identity: String, permission: proto::ParticipantPermission, ) -> Result<()> { - // todo(linux): Remove this once the cross-platform LiveKit implementation is merged - #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; - room.participant_permissions.insert(identity, permission); + room.participant_permissions + .insert(ParticipantIdentity(identity), permission); Ok(()) } pub async fn disconnect_client(&self, client_identity: String) { - // todo(linux): Remove this once the cross-platform LiveKit implementation is merged - #[cfg(any(test, feature = "test-support"))] + let client_identity = ParticipantIdentity(client_identity); + self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); for room in server_rooms.values_mut() { if let Some(room) = room.client_rooms.remove(&client_identity) { - *room.0.lock().connection.0.borrow_mut() = ConnectionState::Disconnected; + let mut room = room.0.lock(); + room.connection_state = ConnectionState::Disconnected; + room.updates_tx + .blocking_send(RoomEvent::Disconnected { + reason: DisconnectReason::SignalClose, + }) + .ok(); } } } @@ -219,13 +284,12 @@ impl TestServer { async fn publish_video_track( &self, token: String, - local_track: LocalVideoTrack, - ) -> Result { - // todo(linux): Remove this once the cross-platform LiveKit implementation is merged - #[cfg(any(test, feature = "test-support"))] + _local_track: LocalVideoTrack, + ) -> Result { self.executor.simulate_random_delay().await; + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; - let identity = claims.sub.unwrap().to_string(); + let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); @@ -244,26 +308,38 @@ impl TestServer { return Err(anyhow!("user is not allowed to publish")); } - let sid = nanoid::nanoid!(17); - let track = Arc::new(TestServerVideoTrack { + let sid: TrackSid = format!("TR_{}", nanoid::nanoid!(17)).try_into().unwrap(); + let server_track = Arc::new(TestServerVideoTrack { sid: sid.clone(), publisher_id: identity.clone(), - frames_rx: local_track.frames_rx.clone(), }); - room.video_tracks.push(track.clone()); + room.video_tracks.push(server_track.clone()); - for (id, client_room) in &room.client_rooms { - if *id != identity { - let _ = client_room + for (room_identity, client_room) in &room.client_rooms { + if *room_identity != identity { + let track = RemoteTrack::Video(RemoteVideoTrack { + server_track: server_track.clone(), + _room: client_room.downgrade(), + }); + let publication = RemoteTrackPublication { + sid: sid.clone(), + room: client_room.downgrade(), + track: track.clone(), + }; + let participant = RemoteParticipant { + identity: identity.clone(), + room: client_room.downgrade(), + }; + client_room .0 .lock() .updates_tx - .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(Arc::new( - RemoteVideoTrack { - server_track: track.clone(), - }, - ))) + .blocking_send(RoomEvent::TrackSubscribed { + track, + publication, + participant, + }) .unwrap(); } } @@ -275,13 +351,11 @@ impl TestServer { &self, token: String, _local_track: &LocalAudioTrack, - ) -> Result { - // todo(linux): Remove this once the cross-platform LiveKit implementation is merged - #[cfg(any(test, feature = "test-support"))] + ) -> Result { self.executor.simulate_random_delay().await; let claims = live_kit_server::token::validate(&token, &self.secret_key)?; - let identity = claims.sub.unwrap().to_string(); + let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); @@ -300,41 +374,54 @@ impl TestServer { return Err(anyhow!("user is not allowed to publish")); } - let sid = nanoid::nanoid!(17); - let track = Arc::new(TestServerAudioTrack { + let sid: TrackSid = format!("TR_{}", nanoid::nanoid!(17)).try_into().unwrap(); + let server_track = Arc::new(TestServerAudioTrack { sid: sid.clone(), publisher_id: identity.clone(), muted: AtomicBool::new(false), }); - let publication = Arc::new(RemoteTrackPublication); + room.audio_tracks.push(server_track.clone()); - room.audio_tracks.push(track.clone()); - - for (id, client_room) in &room.client_rooms { - if *id != identity { - let _ = client_room + for (room_identity, client_room) in &room.client_rooms { + if *room_identity != identity { + let track = RemoteTrack::Audio(RemoteAudioTrack { + server_track: server_track.clone(), + room: client_room.downgrade(), + }); + let publication = RemoteTrackPublication { + sid: sid.clone(), + room: client_room.downgrade(), + track: track.clone(), + }; + let participant = RemoteParticipant { + identity: identity.clone(), + room: client_room.downgrade(), + }; + client_room .0 .lock() .updates_tx - .try_broadcast(RoomUpdate::SubscribedToRemoteAudioTrack( - Arc::new(RemoteAudioTrack { - server_track: track.clone(), - room: Arc::downgrade(client_room), - }), - publication.clone(), - )) - .unwrap(); + .blocking_send(RoomEvent::TrackSubscribed { + track, + publication, + participant, + }) + .ok(); } } Ok(sid) } - fn set_track_muted(&self, token: &str, track_sid: &str, muted: bool) -> Result<()> { - let claims = live_kit_server::token::validate(token, &self.secret_key)?; + async fn unpublish_track(&self, _token: String, _track: &TrackSid) -> Result<()> { + Ok(()) + } + + fn set_track_muted(&self, token: &str, track_sid: &TrackSid, muted: bool) -> Result<()> { + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let room_name = claims.video.room.unwrap(); - let identity = claims.sub.unwrap(); + let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) @@ -342,19 +429,42 @@ impl TestServer { if let Some(track) = room .audio_tracks .iter_mut() - .find(|track| track.sid == track_sid) + .find(|track| track.sid == *track_sid) { track.muted.store(muted, SeqCst); for (id, client_room) in room.client_rooms.iter() { if *id != identity { + let participant = Participant::Remote(RemoteParticipant { + identity: identity.clone(), + room: client_room.downgrade(), + }); + let track = RemoteTrack::Audio(RemoteAudioTrack { + server_track: track.clone(), + room: client_room.downgrade(), + }); + let publication = TrackPublication::Remote(RemoteTrackPublication { + sid: track_sid.clone(), + room: client_room.downgrade(), + track, + }); + + let event = if muted { + RoomEvent::TrackMuted { + participant, + publication, + } + } else { + RoomEvent::TrackUnmuted { + participant, + publication, + } + }; + client_room .0 .lock() .updates_tx - .try_broadcast(RoomUpdate::RemoteAudioTrackMuteChanged { - track_id: track_sid.to_string(), - muted, - }) + .blocking_send(event) .unwrap(); } } @@ -362,14 +472,14 @@ impl TestServer { Ok(()) } - fn is_track_muted(&self, token: &str, track_sid: &str) -> Option { - let claims = live_kit_server::token::validate(token, &self.secret_key).ok()?; + fn is_track_muted(&self, token: &str, track_sid: &TrackSid) -> Option { + let claims = live_kit_server::token::validate(&token, &self.secret_key).ok()?; let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); let room = server_rooms.get_mut(&*room_name)?; room.audio_tracks.iter().find_map(|track| { - if track.sid == track_sid { + if track.sid == *track_sid { Some(track.muted.load(SeqCst)) } else { None @@ -377,33 +487,10 @@ impl TestServer { }) } - fn video_tracks(&self, token: String) -> Result>> { + fn video_tracks(&self, token: String) -> Result> { let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let room_name = claims.video.room.unwrap(); - let identity = claims.sub.unwrap(); - - let mut server_rooms = self.rooms.lock(); - let room = server_rooms - .get_mut(&*room_name) - .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; - room.client_rooms - .get(identity.as_ref()) - .ok_or_else(|| anyhow!("not a participant in room"))?; - Ok(room - .video_tracks - .iter() - .map(|track| { - Arc::new(RemoteVideoTrack { - server_track: track.clone(), - }) - }) - .collect()) - } - - fn audio_tracks(&self, token: String) -> Result>> { - let claims = live_kit_server::token::validate(&token, &self.secret_key)?; - let room_name = claims.video.room.unwrap(); - let identity = claims.sub.unwrap(); + let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); let mut server_rooms = self.rooms.lock(); let room = server_rooms @@ -411,49 +498,148 @@ impl TestServer { .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; let client_room = room .client_rooms - .get(identity.as_ref()) + .get(&identity) + .ok_or_else(|| anyhow!("not a participant in room"))?; + Ok(room + .video_tracks + .iter() + .map(|track| RemoteVideoTrack { + server_track: track.clone(), + _room: client_room.downgrade(), + }) + .collect()) + } + + fn audio_tracks(&self, token: String) -> Result> { + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let room_name = claims.video.room.unwrap(); + let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); + + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + let client_room = room + .client_rooms + .get(&identity) .ok_or_else(|| anyhow!("not a participant in room"))?; Ok(room .audio_tracks .iter() - .map(|track| { - Arc::new(RemoteAudioTrack { - server_track: track.clone(), - room: Arc::downgrade(client_room), - }) + .map(|track| RemoteAudioTrack { + server_track: track.clone(), + room: client_room.downgrade(), }) .collect()) } } -#[derive(Default)] +#[cfg(not(target_os = "windows"))] +#[derive(Default, Debug)] struct TestServerRoom { - client_rooms: HashMap>, + client_rooms: HashMap, video_tracks: Vec>, audio_tracks: Vec>, - participant_permissions: HashMap, + participant_permissions: HashMap, } +#[cfg(not(target_os = "windows"))] #[derive(Debug)] struct TestServerVideoTrack { - sid: Sid, - publisher_id: Sid, - frames_rx: async_broadcast::Receiver, + sid: TrackSid, + publisher_id: ParticipantIdentity, + // frames_rx: async_broadcast::Receiver, } +#[cfg(not(target_os = "windows"))] #[derive(Debug)] struct TestServerAudioTrack { - sid: Sid, - publisher_id: Sid, + sid: TrackSid, + publisher_id: ParticipantIdentity, muted: AtomicBool, } -impl TestServerRoom {} - pub struct TestApiClient { url: String, } +#[derive(Clone, Debug)] +#[non_exhaustive] +pub enum RoomEvent { + ParticipantConnected(RemoteParticipant), + ParticipantDisconnected(RemoteParticipant), + LocalTrackPublished { + publication: LocalTrackPublication, + track: LocalTrack, + participant: LocalParticipant, + }, + LocalTrackUnpublished { + publication: LocalTrackPublication, + participant: LocalParticipant, + }, + TrackSubscribed { + track: RemoteTrack, + publication: RemoteTrackPublication, + participant: RemoteParticipant, + }, + TrackUnsubscribed { + track: RemoteTrack, + publication: RemoteTrackPublication, + participant: RemoteParticipant, + }, + TrackSubscriptionFailed { + participant: RemoteParticipant, + error: String, + #[cfg(not(target_os = "windows"))] + track_sid: TrackSid, + }, + TrackPublished { + publication: RemoteTrackPublication, + participant: RemoteParticipant, + }, + TrackUnpublished { + publication: RemoteTrackPublication, + participant: RemoteParticipant, + }, + TrackMuted { + participant: Participant, + publication: TrackPublication, + }, + TrackUnmuted { + participant: Participant, + publication: TrackPublication, + }, + RoomMetadataChanged { + old_metadata: String, + metadata: String, + }, + ParticipantMetadataChanged { + participant: Participant, + old_metadata: String, + metadata: String, + }, + ParticipantNameChanged { + participant: Participant, + old_name: String, + name: String, + }, + ActiveSpeakersChanged { + speakers: Vec, + }, + #[cfg(not(target_os = "windows"))] + ConnectionStateChanged(ConnectionState), + Connected { + participants_with_tracks: Vec<(RemoteParticipant, Vec)>, + }, + #[cfg(not(target_os = "windows"))] + Disconnected { + reason: DisconnectReason, + }, + Reconnecting, + Reconnected, +} + +#[cfg(not(target_os = "windows"))] #[async_trait] impl live_kit_server::api::Client for TestApiClient { fn url(&self) -> &str { @@ -474,7 +660,9 @@ impl live_kit_server::api::Client for TestApiClient { async fn remove_participant(&self, room: String, identity: String) -> Result<()> { let server = TestServer::get(&self.url)?; - server.remove_participant(room, identity).await?; + server + .remove_participant(room, ParticipantIdentity(identity)) + .await?; Ok(()) } @@ -513,370 +701,125 @@ impl live_kit_server::api::Client for TestApiClient { } struct RoomState { - connection: ( - watch::Sender, - watch::Receiver, - ), - display_sources: Vec, - paused_audio_tracks: HashSet, - updates_tx: async_broadcast::Sender, - updates_rx: async_broadcast::Receiver, + url: String, + token: String, + #[cfg(not(target_os = "windows"))] + local_identity: ParticipantIdentity, + #[cfg(not(target_os = "windows"))] + connection_state: ConnectionState, + #[cfg(not(target_os = "windows"))] + paused_audio_tracks: HashSet, + updates_tx: mpsc::Sender, } -pub struct Room(Mutex); +#[derive(Clone, Debug)] +pub struct Room(Arc>); +#[derive(Clone, Debug)] +pub(crate) struct WeakRoom(Weak>); + +#[cfg(not(target_os = "windows"))] +impl std::fmt::Debug for RoomState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Room") + .field("url", &self.url) + .field("token", &self.token) + .field("local_identity", &self.local_identity) + .field("connection_state", &self.connection_state) + .field("paused_audio_tracks", &self.paused_audio_tracks) + .finish() + } +} + +#[cfg(target_os = "windows")] +impl std::fmt::Debug for RoomState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Room") + .field("url", &self.url) + .field("token", &self.token) + .finish() + } +} + +#[cfg(not(target_os = "windows"))] impl Room { - pub fn new() -> Arc { - let (updates_tx, updates_rx) = async_broadcast::broadcast(128); - Arc::new(Self(Mutex::new(RoomState { - connection: watch::channel_with(ConnectionState::Disconnected), - display_sources: Default::default(), + fn downgrade(&self) -> WeakRoom { + WeakRoom(Arc::downgrade(&self.0)) + } + + pub fn connection_state(&self) -> ConnectionState { + self.0.lock().connection_state + } + + pub fn local_participant(&self) -> LocalParticipant { + let identity = self.0.lock().local_identity.clone(); + LocalParticipant { + identity, + room: self.clone(), + } + } + + pub async fn connect( + url: &str, + token: &str, + _options: RoomOptions, + ) -> Result<(Self, mpsc::Receiver)> { + let server = TestServer::get(&url)?; + let (updates_tx, updates_rx) = mpsc::channel(1024); + let this = Self(Arc::new(Mutex::new(RoomState { + local_identity: ParticipantIdentity(String::new()), + url: url.to_string(), + token: token.to_string(), + connection_state: ConnectionState::Disconnected, paused_audio_tracks: Default::default(), updates_tx, - updates_rx, - }))) - } + }))); - pub fn status(&self) -> watch::Receiver { - self.0.lock().connection.1.clone() - } - - pub fn connect(self: &Arc, url: &str, token: &str) -> impl Future> { - let this = self.clone(); - let url = url.to_string(); - let token = token.to_string(); - async move { - let server = TestServer::get(&url)?; - server - .join_room(token.clone(), this.clone()) - .await - .context("room join")?; - *this.0.lock().connection.0.borrow_mut() = ConnectionState::Connected { url, token }; - Ok(()) - } - } - - pub fn display_sources(self: &Arc) -> impl Future>> { - let this = self.clone(); - async move { - // todo(linux): Remove this once the cross-platform LiveKit implementation is merged - #[cfg(any(test, feature = "test-support"))] - { - let server = this.test_server(); - server.executor.simulate_random_delay().await; - } - - Ok(this.0.lock().display_sources.clone()) - } - } - - pub fn publish_video_track( - self: &Arc, - track: LocalVideoTrack, - ) -> impl Future> { - let this = self.clone(); - let track = track.clone(); - async move { - let sid = this - .test_server() - .publish_video_track(this.token(), track) - .await?; - Ok(LocalTrackPublication { - room: Arc::downgrade(&this), - sid, - }) - } - } - - pub fn publish_audio_track( - self: &Arc, - track: LocalAudioTrack, - ) -> impl Future> { - let this = self.clone(); - let track = track.clone(); - async move { - let sid = this - .test_server() - .publish_audio_track(this.token(), &track) - .await?; - Ok(LocalTrackPublication { - room: Arc::downgrade(&this), - sid, - }) - } - } - - pub fn unpublish_track(&self, _publication: LocalTrackPublication) {} - - pub fn remote_audio_tracks(&self, publisher_id: &str) -> Vec> { - if !self.is_connected() { - return Vec::new(); + let identity = server + .join_room(token.to_string(), this.clone()) + .await + .context("room join")?; + { + let mut state = this.0.lock(); + state.local_identity = identity; + state.connection_state = ConnectionState::Connected; } + Ok((this, updates_rx)) + } + + pub fn remote_participants(&self) -> HashMap { self.test_server() - .audio_tracks(self.token()) + .remote_participants(self.0.lock().token.clone()) .unwrap() - .into_iter() - .filter(|track| track.publisher_id() == publisher_id) - .collect() - } - - pub fn remote_audio_track_publications( - &self, - publisher_id: &str, - ) -> Vec> { - if !self.is_connected() { - return Vec::new(); - } - - self.test_server() - .audio_tracks(self.token()) - .unwrap() - .into_iter() - .filter(|track| track.publisher_id() == publisher_id) - .map(|_track| Arc::new(RemoteTrackPublication {})) - .collect() - } - - pub fn remote_video_tracks(&self, publisher_id: &str) -> Vec> { - if !self.is_connected() { - return Vec::new(); - } - - self.test_server() - .video_tracks(self.token()) - .unwrap() - .into_iter() - .filter(|track| track.publisher_id() == publisher_id) - .collect() - } - - pub fn updates(&self) -> impl Stream { - self.0.lock().updates_rx.clone() - } - - pub fn set_display_sources(&self, sources: Vec) { - self.0.lock().display_sources = sources; } fn test_server(&self) -> Arc { - match self.0.lock().connection.1.borrow().clone() { - ConnectionState::Disconnected => panic!("must be connected to call this method"), - ConnectionState::Connected { url, .. } => TestServer::get(&url).unwrap(), - } + TestServer::get(&self.0.lock().url).unwrap() } fn token(&self) -> String { - match self.0.lock().connection.1.borrow().clone() { - ConnectionState::Disconnected => panic!("must be connected to call this method"), - ConnectionState::Connected { token, .. } => token, - } - } - - fn is_connected(&self) -> bool { - match *self.0.lock().connection.1.borrow() { - ConnectionState::Disconnected => false, - ConnectionState::Connected { .. } => true, - } + self.0.lock().token.clone() } } -impl Drop for Room { +#[cfg(not(target_os = "windows"))] +impl Drop for RoomState { fn drop(&mut self) { - if let ConnectionState::Connected { token, .. } = mem::replace( - &mut *self.0.lock().connection.0.borrow_mut(), - ConnectionState::Disconnected, - ) { - if let Ok(server) = TestServer::get(&token) { + if self.connection_state == ConnectionState::Connected { + if let Ok(server) = TestServer::get(&self.url) { let executor = server.executor.clone(); + let token = self.token.clone(); executor - .spawn(async move { server.leave_room(token).await.unwrap() }) + .spawn(async move { server.leave_room(token).await.ok() }) .detach(); } } } } -#[derive(Clone)] -pub struct LocalTrackPublication { - sid: String, - room: Weak, -} - -impl LocalTrackPublication { - pub fn set_mute(&self, mute: bool) -> impl Future> { - let sid = self.sid.clone(); - let room = self.room.clone(); - async move { - if let Some(room) = room.upgrade() { - room.test_server() - .set_track_muted(&room.token(), &sid, mute) - } else { - Err(anyhow!("no such room")) - } - } - } - - pub fn is_muted(&self) -> bool { - if let Some(room) = self.room.upgrade() { - room.test_server() - .is_track_muted(&room.token(), &self.sid) - .unwrap_or(false) - } else { - false - } - } - - pub fn sid(&self) -> String { - self.sid.clone() - } -} - -pub struct RemoteTrackPublication; - -impl RemoteTrackPublication { - pub fn set_enabled(&self, _enabled: bool) -> impl Future> { - async { Ok(()) } - } - - pub fn is_muted(&self) -> bool { - false - } - - pub fn sid(&self) -> String { - "".to_string() - } -} - -#[derive(Clone)] -pub struct LocalVideoTrack { - frames_rx: async_broadcast::Receiver, -} - -impl LocalVideoTrack { - pub fn screen_share_for_display(display: &MacOSDisplay) -> Self { - Self { - frames_rx: display.frames.1.clone(), - } - } -} - -#[derive(Clone)] -pub struct LocalAudioTrack; - -impl LocalAudioTrack { - pub fn create() -> Self { - Self - } -} - -#[derive(Debug)] -pub struct RemoteVideoTrack { - server_track: Arc, -} - -impl RemoteVideoTrack { - pub fn sid(&self) -> &str { - &self.server_track.sid - } - - pub fn publisher_id(&self) -> &str { - &self.server_track.publisher_id - } - - pub fn frames(&self) -> async_broadcast::Receiver { - self.server_track.frames_rx.clone() - } -} - -#[derive(Debug)] -pub struct RemoteAudioTrack { - server_track: Arc, - room: Weak, -} - -impl RemoteAudioTrack { - pub fn sid(&self) -> &str { - &self.server_track.sid - } - - pub fn publisher_id(&self) -> &str { - &self.server_track.publisher_id - } - - pub fn start(&self) { - if let Some(room) = self.room.upgrade() { - room.0 - .lock() - .paused_audio_tracks - .remove(&self.server_track.sid); - } - } - - pub fn stop(&self) { - if let Some(room) = self.room.upgrade() { - room.0 - .lock() - .paused_audio_tracks - .insert(self.server_track.sid.clone()); - } - } - - pub fn is_playing(&self) -> bool { - !self - .room - .upgrade() - .unwrap() - .0 - .lock() - .paused_audio_tracks - .contains(&self.server_track.sid) - } -} - -#[derive(Clone)] -pub struct MacOSDisplay { - frames: ( - async_broadcast::Sender, - async_broadcast::Receiver, - ), -} - -impl Default for MacOSDisplay { - fn default() -> Self { - Self::new() - } -} - -impl MacOSDisplay { - pub fn new() -> Self { - Self { - frames: async_broadcast::broadcast(128), - } - } - - pub fn send_frame(&self, frame: Frame) { - self.frames.0.try_broadcast(frame).unwrap(); - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Frame { - pub label: String, - pub width: usize, - pub height: usize, -} - -impl Frame { - pub fn width(&self) -> usize { - self.width - } - - pub fn height(&self) -> usize { - self.height - } - - pub fn image(&self) -> SurfaceSource { - unimplemented!("you can't call this in test mode") +impl WeakRoom { + fn upgrade(&self) -> Option { + self.0.upgrade().map(Room) } } diff --git a/crates/live_kit_client/src/test/participant.rs b/crates/live_kit_client/src/test/participant.rs new file mode 100644 index 0000000000..8d476b1537 --- /dev/null +++ b/crates/live_kit_client/src/test/participant.rs @@ -0,0 +1,111 @@ +use super::*; + +#[derive(Clone, Debug)] +pub enum Participant { + Local(LocalParticipant), + Remote(RemoteParticipant), +} + +#[derive(Clone, Debug)] +pub struct LocalParticipant { + #[cfg(not(target_os = "windows"))] + pub(super) identity: ParticipantIdentity, + pub(super) room: Room, +} + +#[derive(Clone, Debug)] +pub struct RemoteParticipant { + #[cfg(not(target_os = "windows"))] + pub(super) identity: ParticipantIdentity, + pub(super) room: WeakRoom, +} + +#[cfg(not(target_os = "windows"))] +impl Participant { + pub fn identity(&self) -> ParticipantIdentity { + match self { + Participant::Local(participant) => participant.identity.clone(), + Participant::Remote(participant) => participant.identity.clone(), + } + } +} + +#[cfg(not(target_os = "windows"))] +impl LocalParticipant { + pub async fn unpublish_track(&self, track: &TrackSid) -> Result<()> { + self.room + .test_server() + .unpublish_track(self.room.token(), track) + .await + } + + pub async fn publish_track( + &self, + track: LocalTrack, + _options: TrackPublishOptions, + ) -> Result { + let this = self.clone(); + let track = track.clone(); + let server = this.room.test_server(); + let sid = match track { + LocalTrack::Video(track) => { + server.publish_video_track(this.room.token(), track).await? + } + LocalTrack::Audio(track) => { + server + .publish_audio_track(this.room.token(), &track) + .await? + } + }; + Ok(LocalTrackPublication { + room: self.room.downgrade(), + sid, + }) + } +} + +#[cfg(not(target_os = "windows"))] +impl RemoteParticipant { + pub fn track_publications(&self) -> HashMap { + if let Some(room) = self.room.upgrade() { + let server = room.test_server(); + let audio = server + .audio_tracks(room.token()) + .unwrap() + .into_iter() + .filter(|track| track.publisher_id() == self.identity) + .map(|track| { + ( + track.sid(), + RemoteTrackPublication { + sid: track.sid(), + room: self.room.clone(), + track: RemoteTrack::Audio(track), + }, + ) + }); + let video = server + .video_tracks(room.token()) + .unwrap() + .into_iter() + .filter(|track| track.publisher_id() == self.identity) + .map(|track| { + ( + track.sid(), + RemoteTrackPublication { + sid: track.sid(), + room: self.room.clone(), + track: RemoteTrack::Video(track), + }, + ) + }); + audio.chain(video).collect() + } else { + HashMap::default() + } + } + + pub fn identity(&self) -> ParticipantIdentity { + self.identity.clone() + } +} diff --git a/crates/live_kit_client/src/test/publication.rs b/crates/live_kit_client/src/test/publication.rs new file mode 100644 index 0000000000..6a3dfa0a51 --- /dev/null +++ b/crates/live_kit_client/src/test/publication.rs @@ -0,0 +1,116 @@ +use super::*; + +#[derive(Clone, Debug)] +pub enum TrackPublication { + Local(LocalTrackPublication), + Remote(RemoteTrackPublication), +} + +#[derive(Clone, Debug)] +pub struct LocalTrackPublication { + #[cfg(not(target_os = "windows"))] + pub(crate) sid: TrackSid, + pub(crate) room: WeakRoom, +} + +#[derive(Clone, Debug)] +pub struct RemoteTrackPublication { + #[cfg(not(target_os = "windows"))] + pub(crate) sid: TrackSid, + pub(crate) room: WeakRoom, + pub(crate) track: RemoteTrack, +} + +#[cfg(not(target_os = "windows"))] +impl TrackPublication { + pub fn sid(&self) -> TrackSid { + match self { + TrackPublication::Local(track) => track.sid(), + TrackPublication::Remote(track) => track.sid(), + } + } + + pub fn is_muted(&self) -> bool { + match self { + TrackPublication::Local(track) => track.is_muted(), + TrackPublication::Remote(track) => track.is_muted(), + } + } +} + +#[cfg(not(target_os = "windows"))] +impl LocalTrackPublication { + pub fn sid(&self) -> TrackSid { + self.sid.clone() + } + + pub fn mute(&self) { + self.set_mute(true) + } + + pub fn unmute(&self) { + self.set_mute(false) + } + + fn set_mute(&self, mute: bool) { + if let Some(room) = self.room.upgrade() { + room.test_server() + .set_track_muted(&room.token(), &self.sid, mute) + .ok(); + } + } + + pub fn is_muted(&self) -> bool { + if let Some(room) = self.room.upgrade() { + room.test_server() + .is_track_muted(&room.token(), &self.sid) + .unwrap_or(false) + } else { + false + } + } +} + +#[cfg(not(target_os = "windows"))] +impl RemoteTrackPublication { + pub fn sid(&self) -> TrackSid { + self.sid.clone() + } + + pub fn track(&self) -> Option { + Some(self.track.clone()) + } + + pub fn kind(&self) -> TrackKind { + self.track.kind() + } + + pub fn is_muted(&self) -> bool { + if let Some(room) = self.room.upgrade() { + room.test_server() + .is_track_muted(&room.token(), &self.sid) + .unwrap_or(false) + } else { + false + } + } + + pub fn is_enabled(&self) -> bool { + if let Some(room) = self.room.upgrade() { + !room.0.lock().paused_audio_tracks.contains(&self.sid) + } else { + false + } + } + + pub fn set_enabled(&self, enabled: bool) { + if let Some(room) = self.room.upgrade() { + let paused_audio_tracks = &mut room.0.lock().paused_audio_tracks; + if enabled { + paused_audio_tracks.remove(&self.sid); + } else { + paused_audio_tracks.insert(self.sid.clone()); + } + } + } +} diff --git a/crates/live_kit_client/src/test/track.rs b/crates/live_kit_client/src/test/track.rs new file mode 100644 index 0000000000..302177a10a --- /dev/null +++ b/crates/live_kit_client/src/test/track.rs @@ -0,0 +1,201 @@ +use super::*; +#[cfg(not(windows))] +use webrtc::{audio_source::RtcAudioSource, video_source::RtcVideoSource}; + +#[cfg(not(windows))] +pub use livekit::track::{TrackKind, TrackSource}; + +#[derive(Clone, Debug)] +pub enum LocalTrack { + Audio(LocalAudioTrack), + Video(LocalVideoTrack), +} + +#[derive(Clone, Debug)] +pub enum RemoteTrack { + Audio(RemoteAudioTrack), + Video(RemoteVideoTrack), +} + +#[derive(Clone, Debug)] +pub struct LocalVideoTrack {} + +#[derive(Clone, Debug)] +pub struct LocalAudioTrack {} + +#[derive(Clone, Debug)] +pub struct RemoteVideoTrack { + #[cfg(not(target_os = "windows"))] + pub(super) server_track: Arc, + pub(super) _room: WeakRoom, +} + +#[derive(Clone, Debug)] +pub struct RemoteAudioTrack { + #[cfg(not(target_os = "windows"))] + pub(super) server_track: Arc, + pub(super) room: WeakRoom, +} + +pub enum RtcTrack { + Audio(RtcAudioTrack), + Video(RtcVideoTrack), +} + +pub struct RtcAudioTrack { + #[cfg(not(target_os = "windows"))] + pub(super) server_track: Arc, + pub(super) room: WeakRoom, +} + +pub struct RtcVideoTrack { + #[cfg(not(target_os = "windows"))] + pub(super) _server_track: Arc, +} + +#[cfg(not(target_os = "windows"))] +impl RemoteTrack { + pub fn sid(&self) -> TrackSid { + match self { + RemoteTrack::Audio(track) => track.sid(), + RemoteTrack::Video(track) => track.sid(), + } + } + + pub fn kind(&self) -> TrackKind { + match self { + RemoteTrack::Audio(_) => TrackKind::Audio, + RemoteTrack::Video(_) => TrackKind::Video, + } + } + + pub fn publisher_id(&self) -> ParticipantIdentity { + match self { + RemoteTrack::Audio(track) => track.publisher_id(), + RemoteTrack::Video(track) => track.publisher_id(), + } + } + + pub fn rtc_track(&self) -> RtcTrack { + match self { + RemoteTrack::Audio(track) => RtcTrack::Audio(track.rtc_track()), + RemoteTrack::Video(track) => RtcTrack::Video(track.rtc_track()), + } + } +} + +#[cfg(not(windows))] +impl LocalVideoTrack { + pub fn create_video_track(_name: &str, _source: RtcVideoSource) -> Self { + Self {} + } +} + +#[cfg(not(windows))] +impl LocalAudioTrack { + pub fn create_audio_track(_name: &str, _source: RtcAudioSource) -> Self { + Self {} + } +} + +#[cfg(not(target_os = "windows"))] +impl RemoteAudioTrack { + pub fn sid(&self) -> TrackSid { + self.server_track.sid.clone() + } + + pub fn publisher_id(&self) -> ParticipantIdentity { + self.server_track.publisher_id.clone() + } + + pub fn start(&self) { + if let Some(room) = self.room.upgrade() { + room.0 + .lock() + .paused_audio_tracks + .remove(&self.server_track.sid); + } + } + + pub fn stop(&self) { + if let Some(room) = self.room.upgrade() { + room.0 + .lock() + .paused_audio_tracks + .insert(self.server_track.sid.clone()); + } + } + + pub fn rtc_track(&self) -> RtcAudioTrack { + RtcAudioTrack { + server_track: self.server_track.clone(), + room: self.room.clone(), + } + } +} + +#[cfg(not(target_os = "windows"))] +impl RemoteVideoTrack { + pub fn sid(&self) -> TrackSid { + self.server_track.sid.clone() + } + + pub fn publisher_id(&self) -> ParticipantIdentity { + self.server_track.publisher_id.clone() + } + + pub fn rtc_track(&self) -> RtcVideoTrack { + RtcVideoTrack { + _server_track: self.server_track.clone(), + } + } +} + +#[cfg(not(target_os = "windows"))] +impl RtcTrack { + pub fn enabled(&self) -> bool { + match self { + RtcTrack::Audio(track) => track.enabled(), + RtcTrack::Video(track) => track.enabled(), + } + } + + pub fn set_enabled(&self, enabled: bool) { + match self { + RtcTrack::Audio(track) => track.set_enabled(enabled), + RtcTrack::Video(_) => {} + } + } +} + +#[cfg(not(target_os = "windows"))] +impl RtcAudioTrack { + pub fn set_enabled(&self, enabled: bool) { + if let Some(room) = self.room.upgrade() { + let paused_audio_tracks = &mut room.0.lock().paused_audio_tracks; + if enabled { + paused_audio_tracks.remove(&self.server_track.sid); + } else { + paused_audio_tracks.insert(self.server_track.sid.clone()); + } + } + } + + pub fn enabled(&self) -> bool { + if let Some(room) = self.room.upgrade() { + !room + .0 + .lock() + .paused_audio_tracks + .contains(&self.server_track.sid) + } else { + false + } + } +} + +impl RtcVideoTrack { + pub fn enabled(&self) -> bool { + true + } +} diff --git a/crates/live_kit_client/src/test/webrtc.rs b/crates/live_kit_client/src/test/webrtc.rs new file mode 100644 index 0000000000..6ac06e0484 --- /dev/null +++ b/crates/live_kit_client/src/test/webrtc.rs @@ -0,0 +1,136 @@ +use super::track::{RtcAudioTrack, RtcVideoTrack}; +use futures::Stream; +use livekit::webrtc as real; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +pub mod video_stream { + use super::*; + + pub mod native { + use super::*; + use real::video_frame::BoxVideoFrame; + + pub struct NativeVideoStream { + pub track: RtcVideoTrack, + } + + impl NativeVideoStream { + pub fn new(track: RtcVideoTrack) -> Self { + Self { track } + } + } + + impl Stream for NativeVideoStream { + type Item = BoxVideoFrame; + + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { + Poll::Pending + } + } + } +} + +pub mod audio_stream { + use super::*; + + pub mod native { + use super::*; + use real::audio_frame::AudioFrame; + + pub struct NativeAudioStream { + pub track: RtcAudioTrack, + } + + impl NativeAudioStream { + pub fn new(track: RtcAudioTrack, _sample_rate: i32, _num_channels: i32) -> Self { + Self { track } + } + } + + impl Stream for NativeAudioStream { + type Item = AudioFrame<'static>; + + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { + Poll::Pending + } + } + } +} + +pub mod audio_source { + use super::*; + + pub use real::audio_source::AudioSourceOptions; + + pub mod native { + use std::sync::Arc; + + use super::*; + use real::{audio_frame::AudioFrame, RtcError}; + + #[derive(Clone)] + pub struct NativeAudioSource { + pub options: Arc, + pub sample_rate: u32, + pub num_channels: u32, + } + + impl NativeAudioSource { + pub fn new( + options: AudioSourceOptions, + sample_rate: u32, + num_channels: u32, + _queue_size_ms: u32, + ) -> Self { + Self { + options: Arc::new(options), + sample_rate, + num_channels, + } + } + + pub async fn capture_frame(&self, _frame: &AudioFrame<'_>) -> Result<(), RtcError> { + Ok(()) + } + } + } + + pub enum RtcAudioSource { + Native(native::NativeAudioSource), + } +} + +pub use livekit::webrtc::audio_frame; +pub use livekit::webrtc::video_frame; + +pub mod video_source { + use super::*; + pub use real::video_source::VideoResolution; + + pub struct RTCVideoSource; + + pub mod native { + use super::*; + use real::video_frame::{VideoBuffer, VideoFrame}; + + #[derive(Clone)] + pub struct NativeVideoSource { + pub resolution: VideoResolution, + } + + impl NativeVideoSource { + pub fn new(resolution: super::VideoResolution) -> Self { + Self { resolution } + } + + pub fn capture_frame>(&self, _frame: &VideoFrame) {} + } + } + + pub enum RtcVideoSource { + Native(native::NativeVideoSource), + } +} diff --git a/crates/media/Cargo.toml b/crates/media/Cargo.toml index 92940d1c52..70478eeb75 100644 --- a/crates/media/Cargo.toml +++ b/crates/media/Cargo.toml @@ -17,6 +17,7 @@ anyhow.workspace = true [target.'cfg(target_os = "macos")'.dependencies] core-foundation.workspace = true +ctor.workspace = true foreign-types = "0.5" metal = "0.29" objc = "0.2" diff --git a/crates/media/src/media.rs b/crates/media/src/media.rs index 8757249c31..3f55475589 100644 --- a/crates/media/src/media.rs +++ b/crates/media/src/media.rs @@ -253,11 +253,14 @@ pub mod core_media { } } - pub fn image_buffer(&self) -> CVImageBuffer { + pub fn image_buffer(&self) -> Option { unsafe { - CVImageBuffer::wrap_under_get_rule(CMSampleBufferGetImageBuffer( - self.as_concrete_TypeRef(), - )) + let ptr = CMSampleBufferGetImageBuffer(self.as_concrete_TypeRef()); + if ptr.is_null() { + None + } else { + Some(CVImageBuffer::wrap_under_get_rule(ptr)) + } } } diff --git a/crates/title_bar/src/collab.rs b/crates/title_bar/src/collab.rs index 805c0e7202..f043194a03 100644 --- a/crates/title_bar/src/collab.rs +++ b/crates/title_bar/src/collab.rs @@ -296,9 +296,9 @@ impl TitleBar { let is_muted = room.is_muted(); let is_deafened = room.is_deafened().unwrap_or(false); let is_screen_sharing = room.is_screen_sharing(); - let can_use_microphone = room.can_use_microphone(); + let can_use_microphone = room.can_use_microphone(cx); let can_share_projects = room.can_share_projects(); - let platform_supported = match self.platform_style { + let screen_sharing_supported = match self.platform_style { PlatformStyle::Mac => true, PlatformStyle::Linux | PlatformStyle::Windows => false, }; @@ -365,9 +365,7 @@ impl TitleBar { ) .tooltip(move |cx| { Tooltip::text( - if !platform_supported { - "Cannot share microphone" - } else if is_muted { + if is_muted { "Unmute microphone" } else { "Mute microphone" @@ -377,56 +375,45 @@ impl TitleBar { }) .style(ButtonStyle::Subtle) .icon_size(IconSize::Small) - .selected(platform_supported && is_muted) - .disabled(!platform_supported) + .selected(is_muted) .selected_style(ButtonStyle::Tinted(TintColor::Negative)) .on_click(move |_, cx| { toggle_mute(&Default::default(), cx); }) .into_any_element(), ); + + children.push( + IconButton::new( + "mute-sound", + if is_deafened { + ui::IconName::AudioOff + } else { + ui::IconName::AudioOn + }, + ) + .style(ButtonStyle::Subtle) + .selected_style(ButtonStyle::Tinted(TintColor::Negative)) + .icon_size(IconSize::Small) + .selected(is_deafened) + .tooltip(move |cx| { + Tooltip::with_meta("Deafen Audio", None, "Mic will be muted", cx) + }) + .on_click(move |_, cx| toggle_deafen(&Default::default(), cx)) + .into_any_element(), + ); } - children.push( - IconButton::new( - "mute-sound", - if is_deafened { - ui::IconName::AudioOff - } else { - ui::IconName::AudioOn - }, - ) - .style(ButtonStyle::Subtle) - .selected_style(ButtonStyle::Tinted(TintColor::Negative)) - .icon_size(IconSize::Small) - .selected(is_deafened) - .disabled(!platform_supported) - .tooltip(move |cx| { - if !platform_supported { - Tooltip::text("Cannot share microphone", cx) - } else if can_use_microphone { - Tooltip::with_meta("Deafen Audio", None, "Mic will be muted", cx) - } else { - Tooltip::text("Deafen Audio", cx) - } - }) - .on_click(move |_, cx| toggle_deafen(&Default::default(), cx)) - .into_any_element(), - ); - - if can_share_projects { + if screen_sharing_supported { children.push( IconButton::new("screen-share", ui::IconName::Screen) .style(ButtonStyle::Subtle) .icon_size(IconSize::Small) .selected(is_screen_sharing) - .disabled(!platform_supported) .selected_style(ButtonStyle::Tinted(TintColor::Accent)) .tooltip(move |cx| { Tooltip::text( - if !platform_supported { - "Cannot share screen" - } else if is_screen_sharing { + if is_screen_sharing { "Stop Sharing Screen" } else { "Share Screen" diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index 59df859488..285946cce0 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -2,16 +2,13 @@ use crate::{ item::{Item, ItemEvent}, ItemNavHistory, WorkspaceId, }; -use anyhow::Result; -use call::participant::{Frame, RemoteVideoTrack}; +use call::{RemoteVideoTrack, RemoteVideoTrackView}; use client::{proto::PeerId, User}; -use futures::StreamExt; use gpui::{ - div, surface, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement, - ParentElement, Render, SharedString, Styled, Task, View, ViewContext, VisualContext, - WindowContext, + div, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement, ParentElement, + Render, SharedString, Styled, View, ViewContext, VisualContext, WindowContext, }; -use std::sync::{Arc, Weak}; +use std::sync::Arc; use ui::{prelude::*, Icon, IconName}; pub enum Event { @@ -19,40 +16,30 @@ pub enum Event { } pub struct SharedScreen { - track: Weak, - frame: Option, pub peer_id: PeerId, user: Arc, nav_history: Option, - _maintain_frame: Task>, + view: View, focus: FocusHandle, } impl SharedScreen { pub fn new( - track: &Arc, + track: RemoteVideoTrack, peer_id: PeerId, user: Arc, cx: &mut ViewContext, ) -> Self { - cx.focus_handle(); - let mut frames = track.frames(); + let view = cx.new_view(|cx| RemoteVideoTrackView::new(track.clone(), cx)); + cx.subscribe(&view, |_, _, ev, cx| match ev { + call::RemoteVideoTrackViewEvent::Close => cx.emit(Event::Close), + }) + .detach(); Self { - track: Arc::downgrade(track), - frame: None, + view, peer_id, user, nav_history: Default::default(), - _maintain_frame: cx.spawn(|this, mut cx| async move { - while let Some(frame) = frames.next().await { - this.update(&mut cx, |this, cx| { - this.frame = Some(frame); - cx.notify(); - })?; - } - this.update(&mut cx, |_, cx| cx.emit(Event::Close))?; - Ok(()) - }), focus: cx.focus_handle(), } } @@ -72,11 +59,7 @@ impl Render for SharedScreen { .track_focus(&self.focus) .key_context("SharedScreen") .size_full() - .children( - self.frame - .as_ref() - .map(|frame| surface(frame.image()).size_full()), - ) + .child(self.view.clone()) } } @@ -114,8 +97,13 @@ impl Item for SharedScreen { _workspace_id: Option, cx: &mut ViewContext, ) -> Option> { - let track = self.track.upgrade()?; - Some(cx.new_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx))) + Some(cx.new_view(|cx| Self { + view: self.view.update(cx, |view, cx| view.clone(cx)), + peer_id: self.peer_id, + user: self.user.clone(), + nav_history: Default::default(), + focus: cx.focus_handle(), + })) } fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index aa3d859f8c..047e47e814 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3939,6 +3939,17 @@ impl Workspace { None } + #[cfg(target_os = "windows")] + fn shared_screen_for_peer( + &self, + _peer_id: PeerId, + _pane: &View, + _cx: &mut WindowContext, + ) -> Option> { + None + } + + #[cfg(not(target_os = "windows"))] fn shared_screen_for_peer( &self, peer_id: PeerId, @@ -3957,7 +3968,7 @@ impl Workspace { } } - Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) + Some(cx.new_view(|cx| SharedScreen::new(track, peer_id, user.clone(), cx))) } pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext) { From ac5ecf5487fe8fa9acc7bece84f3bffcb5961fdc Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 15 Nov 2024 14:36:58 -0700 Subject: [PATCH 002/157] Don't log every value (#20744) Release Notes: - N/A --- crates/collab/src/api/events.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 2357d03bab..9d487c8038 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -419,7 +419,6 @@ pub async fn post_events( let mut request = kinesis_client.put_records().stream_name(stream); for row in for_snowflake(request_body.clone(), first_event_at) { if let Some(data) = serde_json::to_vec(&row).log_err() { - println!("{}", String::from_utf8_lossy(&data)); request = request.records( aws_sdk_kinesis::types::PutRecordsRequestEntry::builder() .partition_key(request_body.system_id.clone().unwrap_or_default()) From 369828f51c5157813e73374df29ea8174af09c1a Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Fri, 15 Nov 2024 15:01:16 -0700 Subject: [PATCH 003/157] Require save confirmation and prevent autosave for deleted files (#20742) * `has_conflict` will now return true if the file has been deleted on disk. This is for treating multi-buffers as conflicted, and also blocks auto-save. * `has_deleted_file` is added so that the single-file buffer save can specifically mention the delete conflict. This does not yet handle discard (#20745). Closes #9101 Closes #9568 Closes #20462 Release Notes: - Improved handling of externally deleted files: auto-save will be disabled, multibuffers will treat this as a save conflict, and single buffers will ask for restore confirmation. Co-authored-by: Conrad --- crates/diagnostics/src/diagnostics.rs | 4 ++ crates/editor/src/items.rs | 4 ++ crates/language/src/buffer.rs | 15 +++-- crates/multi_buffer/src/multi_buffer.rs | 13 +++++ crates/workspace/src/item.rs | 8 +++ crates/workspace/src/pane.rs | 76 +++++++++++++++++-------- 6 files changed, 93 insertions(+), 27 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 933bed2711..6f20b91689 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -727,6 +727,10 @@ impl Item for ProjectDiagnosticsEditor { self.excerpts.read(cx).is_dirty(cx) } + fn has_deleted_file(&self, cx: &AppContext) -> bool { + self.excerpts.read(cx).has_deleted_file(cx) + } + fn has_conflict(&self, cx: &AppContext) -> bool { self.excerpts.read(cx).has_conflict(cx) } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index bc8119c829..8057daafa5 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -708,6 +708,10 @@ impl Item for Editor { self.buffer().read(cx).read(cx).is_dirty() } + fn has_deleted_file(&self, cx: &AppContext) -> bool { + self.buffer().read(cx).read(cx).has_deleted_file() + } + fn has_conflict(&self, cx: &AppContext) -> bool { self.buffer().read(cx).read(cx).has_conflict() } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 690b2d15ba..77fc53705f 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1749,13 +1749,20 @@ impl Buffer { .map_or(false, |file| file.is_deleted() || !file.is_created())) } + pub fn is_deleted(&self) -> bool { + self.file.as_ref().map_or(false, |file| file.is_deleted()) + } + /// Checks if the buffer and its file have both changed since the buffer /// was last saved or reloaded. pub fn has_conflict(&self) -> bool { - self.has_conflict - || self.file.as_ref().map_or(false, |file| { - file.mtime() > self.saved_mtime && self.has_unsaved_edits() - }) + if self.has_conflict { + return true; + } + let Some(file) = self.file.as_ref() else { + return false; + }; + file.is_deleted() || (file.mtime() > self.saved_mtime && self.has_unsaved_edits()) } /// Gets a [`Subscription`] that tracks all of the changes to the buffer's text. diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index bdeb579123..5d111d81ce 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -186,6 +186,7 @@ pub struct MultiBufferSnapshot { non_text_state_update_count: usize, edit_count: usize, is_dirty: bool, + has_deleted_file: bool, has_conflict: bool, show_headers: bool, } @@ -494,6 +495,10 @@ impl MultiBuffer { self.read(cx).is_dirty() } + pub fn has_deleted_file(&self, cx: &AppContext) -> bool { + self.read(cx).has_deleted_file() + } + pub fn has_conflict(&self, cx: &AppContext) -> bool { self.read(cx).has_conflict() } @@ -1419,6 +1424,7 @@ impl MultiBuffer { snapshot.excerpts = Default::default(); snapshot.trailing_excerpt_update_count += 1; snapshot.is_dirty = false; + snapshot.has_deleted_file = false; snapshot.has_conflict = false; self.subscriptions.publish_mut([Edit { @@ -2003,6 +2009,7 @@ impl MultiBuffer { let mut excerpts_to_edit = Vec::new(); let mut non_text_state_updated = false; let mut is_dirty = false; + let mut has_deleted_file = false; let mut has_conflict = false; let mut edited = false; let mut buffers = self.buffers.borrow_mut(); @@ -2028,6 +2035,7 @@ impl MultiBuffer { edited |= buffer_edited; non_text_state_updated |= buffer_non_text_state_updated; is_dirty |= buffer.is_dirty(); + has_deleted_file |= buffer.file().map_or(false, |file| file.is_deleted()); has_conflict |= buffer.has_conflict(); } if edited { @@ -2037,6 +2045,7 @@ impl MultiBuffer { snapshot.non_text_state_update_count += 1; } snapshot.is_dirty = is_dirty; + snapshot.has_deleted_file = has_deleted_file; snapshot.has_conflict = has_conflict; excerpts_to_edit.sort_unstable_by_key(|(locator, _, _)| *locator); @@ -3691,6 +3700,10 @@ impl MultiBufferSnapshot { self.is_dirty } + pub fn has_deleted_file(&self) -> bool { + self.has_deleted_file + } + pub fn has_conflict(&self) -> bool { self.has_conflict } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 2f1c900ecf..5f14b9ba62 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -228,6 +228,9 @@ pub trait Item: FocusableView + EventEmitter { fn is_dirty(&self, _: &AppContext) -> bool { false } + fn has_deleted_file(&self, _: &AppContext) -> bool { + false + } fn has_conflict(&self, _: &AppContext) -> bool { false } @@ -405,6 +408,7 @@ pub trait ItemHandle: 'static + Send { fn item_id(&self) -> EntityId; fn to_any(&self) -> AnyView; fn is_dirty(&self, cx: &AppContext) -> bool; + fn has_deleted_file(&self, cx: &AppContext) -> bool; fn has_conflict(&self, cx: &AppContext) -> bool; fn can_save(&self, cx: &AppContext) -> bool; fn save( @@ -768,6 +772,10 @@ impl ItemHandle for View { self.read(cx).is_dirty(cx) } + fn has_deleted_file(&self, cx: &AppContext) -> bool { + self.read(cx).has_deleted_file(cx) + } + fn has_conflict(&self, cx: &AppContext) -> bool { self.read(cx).has_conflict(cx) } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index b21a531922..24e48b53b0 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1541,18 +1541,25 @@ impl Pane { const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?"; + const DELETED_MESSAGE: &str = + "This file has been deleted on disk since you started editing it. Do you want to recreate it?"; + if save_intent == SaveIntent::Skip { return Ok(true); } - let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.update(|cx| { - ( - item.has_conflict(cx), - item.is_dirty(cx), - item.can_save(cx), - item.is_singleton(cx), - ) - })?; + let (mut has_conflict, mut is_dirty, mut can_save, is_singleton, has_deleted_file) = cx + .update(|cx| { + ( + item.has_conflict(cx), + item.is_dirty(cx), + item.can_save(cx), + item.is_singleton(cx), + item.has_deleted_file(cx), + ) + })?; + + let can_save_as = is_singleton; // when saving a single buffer, we ignore whether or not it's dirty. if save_intent == SaveIntent::Save || save_intent == SaveIntent::SaveWithoutFormat { @@ -1572,22 +1579,45 @@ impl Pane { let should_format = save_intent != SaveIntent::SaveWithoutFormat; if has_conflict && can_save { - let answer = pane.update(cx, |pane, cx| { - pane.activate_item(item_ix, true, true, cx); - cx.prompt( - PromptLevel::Warning, - CONFLICT_MESSAGE, - None, - &["Overwrite", "Discard", "Cancel"], - ) - })?; - match answer.await { - Ok(0) => { - pane.update(cx, |_, cx| item.save(should_format, project, cx))? - .await? + if has_deleted_file && is_singleton { + let answer = pane.update(cx, |pane, cx| { + pane.activate_item(item_ix, true, true, cx); + cx.prompt( + PromptLevel::Warning, + DELETED_MESSAGE, + None, + &["Overwrite", "Close", "Cancel"], + ) + })?; + match answer.await { + Ok(0) => { + pane.update(cx, |_, cx| item.save(should_format, project, cx))? + .await? + } + Ok(1) => { + pane.update(cx, |pane, cx| pane.remove_item(item_ix, false, false, cx))?; + } + _ => return Ok(false), + } + return Ok(true); + } else { + let answer = pane.update(cx, |pane, cx| { + pane.activate_item(item_ix, true, true, cx); + cx.prompt( + PromptLevel::Warning, + CONFLICT_MESSAGE, + None, + &["Overwrite", "Discard", "Cancel"], + ) + })?; + match answer.await { + Ok(0) => { + pane.update(cx, |_, cx| item.save(should_format, project, cx))? + .await? + } + Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, + _ => return Ok(false), } - Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, - _ => return Ok(false), } } else if is_dirty && (can_save || can_save_as) { if save_intent == SaveIntent::Close { From 050ce919ba2106f1525b341da8b47112093f0b53 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Fri, 15 Nov 2024 15:12:40 -0700 Subject: [PATCH 004/157] Rename "Overwrite" to "Save" for prompt about recreating a deleted file (#20750) Release Notes: - N/A --- crates/workspace/src/pane.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 24e48b53b0..52018f53c8 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1586,7 +1586,7 @@ impl Pane { PromptLevel::Warning, DELETED_MESSAGE, None, - &["Overwrite", "Close", "Cancel"], + &["Save", "Close", "Cancel"], ) })?; match answer.await { From 4c8c6c08fe35a7773d3870a206d97c76b64f234d Mon Sep 17 00:00:00 2001 From: Wes Higbee Date: Fri, 15 Nov 2024 16:26:50 -0600 Subject: [PATCH 005/157] docs: Fix assistant prompt_overrides template directory (#20434) Update docs to reflect the correct path for prompt handlebars templates. Link to git repo for prompts rather than including an out of date version inline. Co-authored-by: Peter Tripp --- docs/src/assistant/prompting.md | 72 +++------------------------------ 1 file changed, 5 insertions(+), 67 deletions(-) diff --git a/docs/src/assistant/prompting.md b/docs/src/assistant/prompting.md index 9a919816a6..19ae5d107a 100644 --- a/docs/src/assistant/prompting.md +++ b/docs/src/assistant/prompting.md @@ -138,7 +138,6 @@ Zed has the following internal prompt templates: - `content_prompt.hbs`: Used for generating content in the editor. - `terminal_assistant_prompt.hbs`: Used for the terminal assistant feature. - `suggest_edits.hbs`: Used for generating the model instructions for the XML Suggest Edits should return. -- `step_resolution.hbs`: Used for generating the step resolution prompt. At this point it is unknown if we will expand templates further to be user-creatable. @@ -146,78 +145,17 @@ At this point it is unknown if we will expand templates further to be user-creat > **Note:** It is not recommended to override templates unless you know what you are doing. Editing templates will break your assistant if done incorrectly. -Zed allows you to override the default prompts used for various assistant features by placing custom Handlebars (.hbs) templates in your `~/.config/zed/prompts/templates` directory. +Zed allows you to override the default prompts used for various assistant features by placing custom Handlebars (.hbs) templates in your `~/.config/zed/prompt_overrides` directory. The following templates can be overridden: -1. `content_prompt.hbs`: Used for generating content in the editor. - Format: +1. [`content_prompt.hbs`](https://github.com/zed-industries/zed/tree/main/assets/prompts/content_prompt.hbs): Used for generating content in the editor. - ```handlebars - You are an AI programming assistant. Your task is to - {{#if is_insert}}insert{{else}}rewrite{{/if}} - {{content_type}}{{#if language_name}} in {{language_name}}{{/if}} - based on the following context and user request. Context: - {{#if is_truncated}} - [Content truncated...] - {{/if}} - {{document_content}} - {{#if is_truncated}} - [Content truncated...] - {{/if}} +2. [`terminal_assistant_prompt.hbs`](https://github.com/zed-industries/zed/tree/main/assets/prompts/terminal_assistant_prompt.hbs): Used for the terminal assistant feature. - User request: - {{user_prompt}} +3. [`suggest_edits.hbs`](https://github.com/zed-industries/zed/tree/main/assets/prompts/suggest_edits.hbs): Used for generating the model instructions for the XML Suggest Edits should return. - {{#if rewrite_section}} - Please rewrite the section enclosed in - - tags. - {{else}} - Please insert your response at the - - tag. - {{/if}} - - Provide only the - {{content_type}} - content in your response, without any additional explanation. - ``` - -2. `terminal_assistant_prompt.hbs`: Used for the terminal assistant feature. - Format: - - ```handlebars - You are an AI assistant for a terminal emulator. Provide helpful responses to - user queries about terminal commands, file systems, and general computer - usage. System information: - Operating System: - {{os}} - - Architecture: - {{arch}} - {{#if shell}} - - Shell: - {{shell}} - {{/if}} - {{#if working_directory}} - - Current Working Directory: - {{working_directory}} - {{/if}} - - Latest terminal output: - {{#each latest_output}} - {{this}} - {{/each}} - - User query: - {{user_prompt}} - - Provide a clear and concise response to the user's query, considering the - given system information and latest terminal output if relevant. - ``` - -3. `suggest_edits.hbs`: Used for generating the model instructions for the XML Suggest Edits should return. - -4. `step_resolution.hbs`: Used for generating the step resolution prompt. +4. [`project_slash_command.hbs`](https://github.com/zed-industries/zed/tree/main/assets/prompts/project_slash_command.hbs) > **Note:** Be sure you want to override these, as you'll miss out on iteration on our built-in features. This should be primarily used when developing Zed. From 6e296eb4b6b9f7cef8a597f572ff9edf4e58800b Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Fri, 15 Nov 2024 22:27:45 +0000 Subject: [PATCH 006/157] ssh: Use openbsd nc on macOS (#20751) Co-authored-by: Conrad Irwin --- Cargo.lock | 1 + crates/remote/Cargo.toml | 1 + crates/remote/src/ssh_session.rs | 14 +++++++++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 0c4ea47526..ff5d7540d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9986,6 +9986,7 @@ dependencies = [ "tempfile", "thiserror", "util", + "which 6.0.3", ] [[package]] diff --git a/crates/remote/Cargo.toml b/crates/remote/Cargo.toml index 8a199c56f6..69d1d97c59 100644 --- a/crates/remote/Cargo.toml +++ b/crates/remote/Cargo.toml @@ -38,6 +38,7 @@ tempfile.workspace = true thiserror.workspace = true util.workspace = true release_channel.workspace = true +which.workspace = true [dev-dependencies] gpui = { workspace = true, features = ["test-support"] } diff --git a/crates/remote/src/ssh_session.rs b/crates/remote/src/ssh_session.rs index c607f0a0ec..20795be201 100644 --- a/crates/remote/src/ssh_session.rs +++ b/crates/remote/src/ssh_session.rs @@ -1428,9 +1428,21 @@ impl SshRemoteConnection { } }); + anyhow::ensure!( + which::which("nc").is_ok(), + "Cannot find nc, which is required to connect over ssh." + ); + // Create an askpass script that communicates back to this process. let askpass_script = format!( - "{shebang}\n{print_args} | nc -U {askpass_socket} 2> /dev/null \n", + "{shebang}\n{print_args} | {nc} -U {askpass_socket} 2> /dev/null \n", + // on macOS `brew install netcat` provides the GNU netcat implementation + // which does not support -U. + nc = if cfg!(target_os = "macos") { + "/usr/bin/nc" + } else { + "nc" + }, askpass_socket = askpass_socket.display(), print_args = "printf '%s\\0' \"$@\"", shebang = "#!/bin/sh", From f34877334e976319a712aa590f4ed00b0fffbf3b Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Fri, 15 Nov 2024 17:14:49 -0700 Subject: [PATCH 007/157] Fix tab strikethrough logic (#20755) This fix was in downstream commits before splitting out #20711, should have tested locally before merging. Release Notes: - N/A --- crates/editor/src/items.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 8057daafa5..fb26a5654b 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -635,12 +635,13 @@ impl Item for Editor { Some(util::truncate_and_trailoff(description, MAX_TAB_TITLE_LEN)) }); - let is_deleted: bool = self + // Whether the file was saved in the past but is now deleted. + let was_deleted: bool = self .buffer() .read(cx) .as_singleton() .and_then(|buffer| buffer.read(cx).file()) - .map_or(true, |file| file.is_deleted()); + .map_or(false, |file| file.is_deleted() && file.is_created()); h_flex() .gap_2() @@ -648,7 +649,7 @@ impl Item for Editor { Label::new(self.title(cx).to_string()) .color(label_color) .italic(params.preview) - .strikethrough(is_deleted), + .strikethrough(was_deleted), ) .when_some(description, |this, description| { this.child( From 516f7b364213b2bbc1a658295df96443aa885ca0 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 15 Nov 2024 19:12:01 -0800 Subject: [PATCH 008/157] Add Loading and Fallback States to Image Elements (via StyledImage) (#20371) @iamnbutler edit: This pull request enhances the image element by introducing the ability to display loading and fallback states. Changes: - Implemented the loading and fallback states for image elements using `.with_loading` and `.with_fallback` respectively. - Introduced the `StyledImage` trait and `ImageStyle` to enable a fluent API for changing image styles across image types (`Img`, `Stateful`, etc). Example Usage: ```rust fn loading_element() -> impl IntoElement { div().size_full().flex_none().p_0p5().rounded_sm().child( div().size_full().with_animation( "loading-bg", Animation::new(Duration::from_secs(3)) .repeat() .with_easing(pulsating_between(0.04, 0.24)), move |this, delta| this.bg(black().opacity(delta)), ), ) } fn fallback_element() -> impl IntoElement { let fallback_color: Hsla = black().opacity(0.5); div().size_full().flex_none().p_0p5().child( div() .size_full() .flex() .items_center() .justify_center() .rounded_sm() .text_sm() .text_color(fallback_color) .border_1() .border_color(fallback_color) .child("?"), ) } impl Render for ImageLoadingExample { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { img("some/image/path") .id("image-1") .with_fallback(|| Self::fallback_element().into_any_element()) .with_loading(|| Self::loading_element().into_any_element()) } } ``` Note: An `Img` must have an `id` to be able to add a loading state. Release Notes: - N/A --------- Co-authored-by: nate Co-authored-by: michael Co-authored-by: Nate Butler Co-authored-by: Antonio Scandurra --- Cargo.lock | 1922 +++++++++++++---------- crates/assistant/src/assistant_panel.rs | 3 +- crates/gpui/examples/gif_viewer.rs | 6 +- crates/gpui/examples/image/image.rs | 9 +- crates/gpui/examples/image_loading.rs | 214 +++ crates/gpui/src/app.rs | 11 +- crates/gpui/src/asset_cache.rs | 52 +- crates/gpui/src/elements/div.rs | 2 +- crates/gpui/src/elements/img.rs | 456 ++++-- crates/gpui/src/platform.rs | 21 +- crates/gpui/src/prelude.rs | 2 +- crates/gpui/src/svg_renderer.rs | 4 +- crates/gpui/src/window.rs | 33 +- crates/ui/src/components/scrollbar.rs | 4 +- crates/workspace/src/workspace.rs | 2 +- 15 files changed, 1700 insertions(+), 1041 deletions(-) create mode 100644 crates/gpui/examples/image_loading.rs diff --git a/Cargo.lock b/Cargo.lock index ff5d7540d9..440002cb24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ dependencies = [ "auto_update", "editor", "extension_host", - "futures 0.3.30", + "futures 0.3.31", "gpui", "language", "lsp", @@ -23,19 +23,13 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "gimli 0.31.0", + "gimli 0.31.1", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -100,8 +94,8 @@ dependencies = [ "miow", "parking_lot", "piper", - "polling 3.7.3", - "regex-automata 0.4.7", + "polling 3.7.4", + "regex-automata 0.4.9", "rustix-openpty", "serde", "signal-hook", @@ -124,9 +118,9 @@ checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "alsa" @@ -192,9 +186,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -207,36 +201,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -245,13 +239,13 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", - "futures 0.3.30", + "futures 0.3.31", "http_client", "schemars", "serde", "serde_json", "strum 0.25.0", - "thiserror", + "thiserror 1.0.69", "util", ] @@ -278,9 +272,9 @@ dependencies = [ [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "arg_enum_proc_macro" @@ -301,9 +295,9 @@ checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -396,7 +390,7 @@ dependencies = [ "env_logger 0.11.5", "feature_flags", "fs", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "globset", "gpui", @@ -463,7 +457,7 @@ dependencies = [ "collections", "derive_more", "extension", - "futures 0.3.30", + "futures 0.3.31", "gpui", "language", "language_model", @@ -573,14 +567,14 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.1.1", - "futures-lite 2.3.0", + "fastrand 2.2.0", + "futures-lite 2.5.0", "slab", ] @@ -604,7 +598,7 @@ checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ "async-lock 3.4.0", "blocking", - "futures-lite 2.3.0", + "futures-lite 2.5.0", ] [[package]] @@ -615,10 +609,10 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.3.1", "async-executor", - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", "blocking", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "once_cell", ] @@ -644,18 +638,18 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock 3.4.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "parking", - "polling 3.7.3", - "rustix 0.38.35", + "polling 3.7.4", + "rustix 0.38.40", "slab", "tracing", "windows-sys 0.59.0", @@ -689,7 +683,7 @@ checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" dependencies = [ "futures-util", "native-tls", - "thiserror", + "thiserror 1.0.69", "url", ] @@ -710,9 +704,9 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ - "async-io 2.3.4", + "async-io 2.4.0", "blocking", - "futures-lite 2.3.0", + "futures-lite 2.5.0", ] [[package]] @@ -720,7 +714,7 @@ name = "async-pipe" version = "0.1.3" source = "git+https://github.com/zed-industries/async-pipe-rs?rev=82d00a04211cf4e1236029aa03e6b6ce2a74c553#82d00a04211cf4e1236029aa03e6b6ce2a74c553" dependencies = [ - "futures 0.3.30", + "futures 0.3.31", "log", ] @@ -737,28 +731,27 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.48.0", ] [[package]] name = "async-process" -version = "2.2.4" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ "async-channel 2.3.1", - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", "async-signal", "async-task", "blocking", "cfg-if", "event-listener 5.3.1", - "futures-lite 2.3.0", - "rustix 0.38.35", + "futures-lite 2.5.0", + "rustix 0.38.40", "tracing", - "windows-sys 0.59.0", ] [[package]] @@ -789,13 +782,13 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.35", + "rustix 0.38.40", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -803,21 +796,21 @@ dependencies = [ [[package]] name = "async-std" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", - "async-process 1.8.1", + "async-io 2.4.0", + "async-lock 3.4.0", + "async-process 2.3.0", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 1.13.0", + "futures-lite 2.5.0", "gloo-timers", "kv-log-macro", "log", @@ -831,9 +824,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -842,9 +835,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", @@ -867,7 +860,7 @@ dependencies = [ "serde_qs 0.10.1", "smart-default", "smol_str", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -963,9 +956,9 @@ checksum = "00b9f7252833d5ed4b00aa9604b563529dd5e11de9c23615de2dcdf91eb87b52" dependencies = [ "async-compression", "crc32fast", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "pin-project", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -974,7 +967,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-sink", "futures-util", "memchr", @@ -1044,9 +1037,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "av1-grain" @@ -1064,18 +1057,18 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" dependencies = [ "arrayvec", ] [[package]] name = "aws-config" -version = "1.5.5" +version = "1.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e95816a168520d72c0e7680c405a5a8c1fb6a035b4bc4b9d7b0de8e1a941697" +checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1089,8 +1082,8 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.2", - "fastrand 2.1.1", + "bytes 1.8.0", + "fastrand 2.2.0", "hex", "http 0.2.12", "ring", @@ -1128,8 +1121,8 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.2", - "fastrand 2.1.1", + "bytes 1.8.0", + "fastrand 2.2.0", "http 0.2.12", "http-body 0.4.6", "once_cell", @@ -1154,7 +1147,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.2", + "bytes 1.8.0", "http 0.2.12", "once_cell", "regex-lite", @@ -1163,11 +1156,10 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.47.0" +version = "1.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca49303c05d2a740b8a4552fac63a4db6ead84f7e7eeed04761fd3014c26f25" +checksum = "0e531658a0397d22365dfe26c3e1c0c8448bf6a3a2d8a098ded802f2b1261615" dependencies = [ - "ahash 0.8.11", "aws-credential-types", "aws-runtime", "aws-sigv4", @@ -1181,8 +1173,8 @@ dependencies = [ "aws-smithy-types", "aws-smithy-xml", "aws-types", - "bytes 1.7.2", - "fastrand 2.1.1", + "bytes 1.8.0", + "fastrand 2.2.0", "hex", "hmac", "http 0.2.12", @@ -1198,9 +1190,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.40.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5879bec6e74b648ce12f6085e7245417bc5f6d672781028384d2e494be3eb6d" +checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1211,7 +1203,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.2", + "bytes 1.8.0", "http 0.2.12", "once_cell", "regex-lite", @@ -1220,9 +1212,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.41.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef4cd9362f638c22a3b959fd8df292e7e47fdf170270f86246b97109b5f2f7d" +checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1233,7 +1225,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.2", + "bytes 1.8.0", "http 0.2.12", "once_cell", "regex-lite", @@ -1242,9 +1234,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.40.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1e2735d2ab28b35ecbb5496c9d41857f52a0d6a0075bbf6a8af306045ea6f6" +checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1274,7 +1266,7 @@ dependencies = [ "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.7.2", + "bytes 1.8.0", "crypto-bigint 0.5.5", "form_urlencoded", "hex", @@ -1305,13 +1297,13 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.60.12" +version = "0.60.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598b1689d001c4d4dc3cb386adb07d37786783aee3ac4b324bcadac116bf3d23" +checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795" dependencies = [ "aws-smithy-http", "aws-smithy-types", - "bytes 1.7.2", + "bytes 1.8.0", "crc32c", "crc32fast", "hex", @@ -1331,7 +1323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cef7d0a272725f87e51ba2bf89f8c21e4df61b9e49ae1ac367a6d69916ef7c90" dependencies = [ "aws-smithy-types", - "bytes 1.7.2", + "bytes 1.8.0", "crc32fast", ] @@ -1344,7 +1336,7 @@ dependencies = [ "aws-smithy-eventstream", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.7.2", + "bytes 1.8.0", "bytes-utils", "futures-core", "http 0.2.12", @@ -1385,8 +1377,8 @@ dependencies = [ "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.7.2", - "fastrand 2.1.1", + "bytes 1.8.0", + "fastrand 2.2.0", "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", @@ -1410,7 +1402,7 @@ checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" dependencies = [ "aws-smithy-async", "aws-smithy-types", - "bytes 1.7.2", + "bytes 1.8.0", "http 0.2.12", "http 1.1.0", "pin-project-lite", @@ -1426,7 +1418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" dependencies = [ "base64-simd", - "bytes 1.7.2", + "bytes 1.8.0", "bytes-utils", "futures-core", "http 0.2.12", @@ -1447,9 +1439,9 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.8" +version = "0.60.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d123fbc2a4adc3c301652ba8e149bf4bc1d1725affb9784eb20c953ace06bf55" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" dependencies = [ "xmlparser", ] @@ -1478,7 +1470,7 @@ dependencies = [ "axum-core", "base64 0.21.7", "bitflags 1.3.2", - "bytes 1.7.2", + "bytes 1.8.0", "futures-util", "headers", "http 0.2.12", @@ -1511,7 +1503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ "async-trait", - "bytes 1.7.2", + "bytes 1.8.0", "futures-util", "http 0.2.12", "http-body 0.4.6", @@ -1528,7 +1520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9a320103719de37b7b4da4c8eb629d4573f6bcfd3dfe80d3208806895ccf81d" dependencies = [ "axum", - "bytes 1.7.2", + "bytes 1.8.0", "futures-util", "http 0.2.12", "mime", @@ -1551,7 +1543,7 @@ dependencies = [ "addr2line", "cfg-if", "libc", - "miniz_oxide 0.8.0", + "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", @@ -1599,9 +1591,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bigdecimal" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee" +checksum = "8f850665a0385e070b64c38d2354e6c104c8479c59868d1e48a0c13ee2c7a1c1" dependencies = [ "autocfg", "libm", @@ -1620,26 +1612,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.69.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" -dependencies = [ - "bitflags 2.6.0", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn 2.0.87", -] - [[package]] name = "bindgen" version = "0.70.1" @@ -1728,9 +1700,9 @@ dependencies = [ [[package]] name = "bitstream-io" -version = "2.5.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" [[package]] name = "bitvec" @@ -1841,15 +1813,15 @@ dependencies = [ "async-channel 2.3.1", "async-task", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "piper", ] [[package]] name = "borsh" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" dependencies = [ "borsh-derive", "cfg_aliases 0.2.1", @@ -1857,16 +1829,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.87", - "syn_derive", ] [[package]] @@ -1884,20 +1855,20 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.9", "serde", ] [[package]] name = "built" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" +checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" [[package]] name = "bumpalo" @@ -1935,18 +1906,18 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.17.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", @@ -1977,9 +1948,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "bytes-utils" @@ -1987,7 +1958,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "either", ] @@ -2022,7 +1993,7 @@ dependencies = [ "collections", "feature_flags", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "language", @@ -2045,10 +2016,10 @@ checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ "bitflags 2.6.0", "log", - "polling 3.7.3", - "rustix 0.38.35", + "polling 3.7.4", + "rustix 0.38.40", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2058,7 +2029,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix 0.38.35", + "rustix 0.38.40", "wayland-backend", "wayland-client", ] @@ -2074,9 +2045,9 @@ dependencies = [ [[package]] name = "cap-fs-ext" -version = "3.2.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb23061fc1c4ead4e45ca713080fe768e6234e959f5a5c399c39eb41aa34e56e" +checksum = "e16619ada836f12897a72011fe99b03f0025b87a8dbbea4f3c9f89b458a23bf3" dependencies = [ "cap-primitives", "cap-std", @@ -2086,21 +2057,21 @@ dependencies = [ [[package]] name = "cap-net-ext" -version = "3.2.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83ae11f116bcbafc5327c6af250341db96b5930046732e1905f7dc65887e0e1" +checksum = "710b0eb776410a22c89a98f2f80b2187c2ac3a8206b99f3412332e63c9b09de0" dependencies = [ "cap-primitives", "cap-std", - "rustix 0.38.35", + "rustix 0.38.40", "smallvec", ] [[package]] name = "cap-primitives" -version = "3.2.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d00bd8d26c4270d950eaaa837387964a2089a1c3c349a690a1fa03221d29531" +checksum = "82fa6c3f9773feab88d844aa50035a33fb6e7e7426105d2f4bb7aadc42a5f89a" dependencies = [ "ambient-authority", "fs-set-times", @@ -2108,16 +2079,16 @@ dependencies = [ "io-lifetimes 2.0.3", "ipnet", "maybe-owned", - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.52.0", "winx", ] [[package]] name = "cap-rand" -version = "3.2.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcb16a619d8b8211ed61f42bd290d2a1ac71277a69cf8417ec0996fa92f5211" +checksum = "53774d49369892b70184f8312e50c1b87edccb376691de4485b0ff554b27c36c" dependencies = [ "ambient-authority", "rand 0.8.5", @@ -2125,27 +2096,27 @@ dependencies = [ [[package]] name = "cap-std" -version = "3.2.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19eb8e3d71996828751c1ed3908a439639752ac6bdc874e41469ef7fc15fbd7f" +checksum = "7f71b70818556b4fe2a10c7c30baac3f5f45e973f49fc2673d7c75c39d0baf5b" dependencies = [ "cap-primitives", "io-extras", "io-lifetimes 2.0.3", - "rustix 0.38.35", + "rustix 0.38.40", ] [[package]] name = "cap-time-ext" -version = "3.2.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61142dc51e25b7acc970ca578ce2c3695eac22bbba46c1073f5f583e78957725" +checksum = "69dd48afa2363f746c93f961c211f6f099fb594a3446b8097bc5f79db51b6816" dependencies = [ "ambient-authority", "cap-primitives", "iana-time-zone", "once_cell", - "rustix 0.38.35", + "rustix 0.38.40", "winx", ] @@ -2169,7 +2140,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2204,7 +2175,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" dependencies = [ "heck 0.4.1", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "proc-macro2", "quote", @@ -2217,9 +2188,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.15" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "jobserver", "libc", @@ -2277,7 +2248,7 @@ dependencies = [ "client", "clock", "collections", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "language", @@ -2387,9 +2358,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.24" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7db6eca8c205649e8d3ccd05aa5042b1800a784e56bc7c43524fde8abbfa9b" +checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" dependencies = [ "clap", ] @@ -2408,9 +2379,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "cli" @@ -2441,17 +2412,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0875e527e299fc5f4faba42870bf199a39ab0bb2dbba1b8aef0a2151451130f" dependencies = [ "bstr", - "bytes 1.7.2", + "bytes 1.8.0", "clickhouse-derive", "clickhouse-rs-cityhash-sys", - "futures 0.3.30", + "futures 0.3.31", "hyper 0.14.31", "hyper-tls", "lz4", "sealed", "serde", "static_assertions", - "thiserror", + "thiserror 1.0.69", "tokio", "url", ] @@ -2490,7 +2461,7 @@ dependencies = [ "cocoa 0.26.0", "collections", "feature_flags", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "log", @@ -2512,7 +2483,7 @@ dependencies = [ "sysinfo", "telemetry_events", "text", - "thiserror", + "thiserror 1.0.69", "time", "tiny_http", "tokio-socks", @@ -2635,14 +2606,14 @@ dependencies = [ "collections", "context_servers", "ctor", - "dashmap 6.0.1", + "dashmap 6.1.0", "derive_more", "editor", "env_logger 0.11.5", "envy", "file_finder", "fs", - "futures 0.3.30", + "futures 0.3.31", "git", "git_hosting_providers", "google_ai", @@ -2695,7 +2666,7 @@ dependencies = [ "telemetry_events", "text", "theme", - "thiserror", + "thiserror 1.0.69", "time", "tokio", "toml 0.8.19", @@ -2723,7 +2694,7 @@ dependencies = [ "db", "editor", "emojis", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "gpui", "http_client", @@ -2770,9 +2741,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "combine" @@ -2780,7 +2751,7 @@ version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "memchr", ] @@ -2888,7 +2859,7 @@ dependencies = [ "anyhow", "collections", "command_palette_hooks", - "futures 0.3.30", + "futures 0.3.31", "gpui", "log", "parking_lot", @@ -2933,7 +2904,7 @@ dependencies = [ "command_palette_hooks", "editor", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "indoc", @@ -3066,11 +3037,11 @@ dependencies = [ [[package]] name = "coreaudio-sys" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f01585027057ff5f0a5bf276174ae4c1594a2c5bde93d5f46a016d76270f5a9" +checksum = "2ce857aa0b77d77287acc1ac3e37a05a8c95a2af3647d23b15f263bdaeb7562b" dependencies = [ - "bindgen 0.69.4", + "bindgen", ] [[package]] @@ -3128,9 +3099,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] @@ -3493,9 +3464,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", @@ -3648,7 +3619,7 @@ dependencies = [ "fuzzy-matcher", "shell-words", "tempfile", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -3711,6 +3682,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "dlib" version = "0.5.2" @@ -3757,9 +3739,9 @@ dependencies = [ [[package]] name = "dwrote" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da3498378ed373237bdef1eddcc64e7be2d3ba4841f4c22a998e81cadeea83c" +checksum = "70182709525a3632b2ba96b6569225467b18ecb4a77f46d255f713a6bebf05fd" dependencies = [ "lazy_static", "libc", @@ -3808,7 +3790,7 @@ dependencies = [ "emojis", "env_logger 0.11.5", "file_icons", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "git", "gpui", @@ -3854,18 +3836,6 @@ dependencies = [ "workspace", ] -[[package]] -name = "educe" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4bd92664bf78c4d3dba9b7cdafce6fa15b13ed3ed16175218196942e99168a8" -dependencies = [ - "enum-ordinalize", - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "either" version = "1.13.0" @@ -3909,9 +3879,9 @@ dependencies = [ [[package]] name = "embed-resource" -version = "2.4.3" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edcacde9351c33139a41e3c97eb2334351a81a2791bebb0b243df837128f602" +checksum = "b68b6f9f63a0b6a38bc447d4ce84e2b388f3ec95c99c641c8ff0dd3ef89a6379" dependencies = [ "cc", "memchr", @@ -3950,9 +3920,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -3963,26 +3933,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" -[[package]] -name = "enum-ordinalize" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" -dependencies = [ - "enum-ordinalize-derive", -] - -[[package]] -name = "enum-ordinalize-derive" -version = "4.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "enumflags2" version = "0.7.10" @@ -4204,15 +4154,14 @@ dependencies = [ [[package]] name = "exr" -version = "1.72.0" +version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" dependencies = [ "bit_field", - "flume", "half", "lebe", - "miniz_oxide 0.7.4", + "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", @@ -4228,7 +4177,7 @@ dependencies = [ "async-trait", "collections", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "language", @@ -4280,7 +4229,7 @@ dependencies = [ "env_logger 0.11.5", "extension", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "language", @@ -4369,8 +4318,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ "bit-set 0.8.0", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -4390,9 +4339,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fd-lock" @@ -4401,15 +4350,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" dependencies = [ "cfg-if", - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.52.0", ] [[package]] name = "fdeflate" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" dependencies = [ "simd-adler32", ] @@ -4418,7 +4367,7 @@ dependencies = [ name = "feature_flags" version = "0.1.0" dependencies = [ - "futures 0.3.30", + "futures 0.3.31", "gpui", ] @@ -4431,7 +4380,7 @@ dependencies = [ "client", "db", "editor", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "human_bytes", @@ -4472,7 +4421,7 @@ dependencies = [ "editor", "env_logger 0.11.5", "file_icons", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "gpui", "language", @@ -4510,7 +4459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" dependencies = [ "libc", - "thiserror", + "thiserror 1.0.69", "winapi", ] @@ -4534,12 +4483,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -4572,6 +4521,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "font-kit" version = "0.14.1" @@ -4598,9 +4553,9 @@ dependencies = [ [[package]] name = "font-types" -version = "0.6.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0189ccb084f77c5523e08288d418cbaa09c451a08515678a0aa265df9a8b60" +checksum = "b3971f9a5ca983419cdc386941ba3b9e1feba01a0ab888adf78739feb2798492" dependencies = [ "bytemuck", ] @@ -4710,7 +4665,7 @@ dependencies = [ "cocoa 0.26.0", "collections", "fsevent", - "futures 0.3.30", + "futures 0.3.31", "git", "git2", "gpui", @@ -4737,7 +4692,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "033b337d725b97690d86893f9de22b67b80dcc4e9ad815f348254c38119db8fb" dependencies = [ "io-lifetimes 2.0.3", - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.52.0", ] @@ -4804,9 +4759,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -4823,16 +4778,16 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f444c45a1cb86f2a7e301469fd50a82084a60dadc25d94529a8312276ecb71a" dependencies = [ - "futures 0.3.30", + "futures 0.3.31", "futures-timer", "pin-utils", ] [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -4840,15 +4795,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -4868,9 +4823,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -4889,11 +4844,11 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ - "fastrand 2.1.1", + "fastrand 2.2.0", "futures-core", "futures-io", "parking", @@ -4902,9 +4857,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -4913,15 +4868,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" @@ -4931,9 +4886,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures 0.1.31", "futures-channel", @@ -5027,15 +4982,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" dependencies = [ "fallible-iterator", - "indexmap 2.4.0", + "indexmap 2.6.0", "stable_deref_trait", ] [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git" @@ -5084,7 +5039,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "futures 0.3.30", + "futures 0.3.31", "git", "gpui", "http_client", @@ -5112,15 +5067,15 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] name = "gloo-timers" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", @@ -5130,9 +5085,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f865cbd94bd355b89611211e49508da98a1fce0ad755c1e8448fb96711b24528" +checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" dependencies = [ "js-sys", "slotmap", @@ -5170,7 +5125,7 @@ name = "google_ai" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.30", + "futures 0.3.31", "http_client", "schemars", "serde", @@ -5217,7 +5172,7 @@ dependencies = [ "ashpd", "async-task", "backtrace", - "bindgen 0.70.1", + "bindgen", "blade-graphics", "blade-macros", "blade-util", @@ -5242,7 +5197,7 @@ dependencies = [ "flume", "font-kit", "foreign-types 0.5.0", - "futures 0.3.30", + "futures 0.3.31", "gpui_macros", "http_client", "image", @@ -5276,7 +5231,7 @@ dependencies = [ "strum 0.25.0", "sum_tree", "taffy", - "thiserror", + "thiserror 1.0.69", "unicode-segmentation", "usvg", "util", @@ -5327,13 +5282,13 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "fnv", "futures-core", "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.4.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -5347,12 +5302,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", - "bytes 1.7.2", + "bytes 1.8.0", "fnv", "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.4.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -5380,7 +5335,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5395,7 +5350,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5427,6 +5382,17 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashlink" version = "0.8.4" @@ -5452,7 +5418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ "base64 0.21.7", - "bytes 1.7.2", + "bytes 1.8.0", "headers-core", "http 0.2.12", "httpdate", @@ -5631,7 +5597,7 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "fnv", "itoa", ] @@ -5642,7 +5608,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "fnv", "itoa", ] @@ -5653,7 +5619,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "http 0.2.12", "pin-project-lite", ] @@ -5664,7 +5630,7 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "http 1.1.0", ] @@ -5674,7 +5640,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-util", "http 1.1.0", "http-body 1.0.1", @@ -5713,9 +5679,9 @@ name = "http_client" version = "0.1.0" dependencies = [ "anyhow", - "bytes 1.7.2", + "bytes 1.8.0", "derive_more", - "futures 0.3.30", + "futures 0.3.31", "http 1.1.0", "log", "serde", @@ -5725,9 +5691,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -5753,7 +5719,7 @@ version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-channel", "futures-core", "futures-util", @@ -5773,11 +5739,11 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-channel", "futures-util", "h2 0.4.6", @@ -5815,9 +5781,9 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", - "rustls 0.23.13", + "rustls 0.23.16", "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", @@ -5831,7 +5797,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "hyper 0.14.31", "native-tls", "tokio", @@ -5840,16 +5806,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.4.1", + "hyper 1.5.0", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -5859,9 +5825,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -5880,6 +5846,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "id-arena" version = "2.2.1" @@ -5888,12 +5972,23 @@ checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -5906,7 +6001,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.9", "same-file", "walkdir", "winapi-util", @@ -5914,9 +6009,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.2" +version = "0.25.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" dependencies = [ "bytemuck", "byteorder-lite", @@ -5937,9 +6032,9 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" +checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f" dependencies = [ "byteorder-lite", "quick-error", @@ -5969,9 +6064,9 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] name = "imgref" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexed_docs" @@ -5984,7 +6079,7 @@ dependencies = [ "derive_more", "extension", "fs", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "gpui", "heed", @@ -6013,12 +6108,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.1", "serde", ] @@ -6053,7 +6148,7 @@ dependencies = [ "copilot", "editor", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "indoc", "language", @@ -6132,9 +6227,9 @@ dependencies = [ [[package]] name = "io-extras" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9f046b9af244f13b3bd939f55d16830ac3a201e8a9ba9661bfcb03e2be72b9b" +checksum = "7d45fd7584f9b67ac37bc041212d06bfac0700b36456b05890d36a3b626260eb" dependencies = [ "io-lifetimes 2.0.3", "windows-sys 0.52.0", @@ -6187,9 +6282,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-docker" @@ -6280,7 +6375,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -6324,9 +6419,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -6355,7 +6450,7 @@ dependencies = [ "anyhow", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "uuid", ] @@ -6391,9 +6486,9 @@ dependencies = [ [[package]] name = "kurbo" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" dependencies = [ "arrayvec", "smallvec", @@ -6420,7 +6515,7 @@ dependencies = [ "ctor", "ec4rs", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "git", "globset", @@ -6478,7 +6573,7 @@ dependencies = [ "editor", "env_logger 0.11.5", "feature_flags", - "futures 0.3.30", + "futures 0.3.31", "google_ai", "gpui", "http_client", @@ -6502,7 +6597,7 @@ dependencies = [ "telemetry_events", "text", "theme", - "thiserror", + "thiserror 1.0.69", "tiktoken-rs", "ui", "unindent", @@ -6535,7 +6630,7 @@ dependencies = [ "copilot", "editor", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "gpui", "itertools 0.13.0", "language", @@ -6561,7 +6656,7 @@ dependencies = [ "async-tar", "async-trait", "collections", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "language", @@ -6618,12 +6713,6 @@ dependencies = [ "spin", ] -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "leb128" version = "0.2.5" @@ -6654,13 +6743,12 @@ dependencies = [ [[package]] name = "libfuzzer-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" dependencies = [ "arbitrary", "cc", - "once_cell", ] [[package]] @@ -6687,9 +6775,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libmimalloc-sys" @@ -6709,7 +6797,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.3", + "redox_syscall 0.5.7", ] [[package]] @@ -6738,7 +6826,7 @@ dependencies = [ "parking_lot", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "wasm-bindgen", "wasm-bindgen-futures", @@ -6808,6 +6896,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "live_kit_client" version = "0.1.0" @@ -6817,7 +6911,7 @@ dependencies = [ "collections", "core-foundation 0.9.4", "cpal", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http 0.2.12", "http_client", @@ -6868,7 +6962,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -6891,7 +6985,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-tungstenite 0.20.1", "url", @@ -6910,7 +7004,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.6", "serde", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -6919,10 +7013,10 @@ name = "livekit-runtime" version = "0.3.1" source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" dependencies = [ - "async-io 2.3.4", + "async-io 2.4.0", "async-std", "async-task", - "futures 0.3.30", + "futures 0.3.31", ] [[package]] @@ -6967,11 +7061,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.1", ] [[package]] @@ -6983,7 +7077,7 @@ dependencies = [ "collections", "ctor", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "gpui", "log", "lsp-types", @@ -7012,19 +7106,18 @@ dependencies = [ [[package]] name = "lz4" -version = "1.26.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958b4caa893816eea05507c20cfe47574a43d9a697138a7872990bba8a0ece68" +checksum = "4d1febb2b4a79ddd1980eede06a8f7902197960aa0383ffcfdd62fe723036725" dependencies = [ - "libc", "lz4-sys", ] [[package]] name = "lz4-sys" -version = "1.10.0" +version = "1.11.1+lz4-1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109de74d5d2353660401699a4174a4ff23fcc649caf553df71933c7fb45ad868" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" dependencies = [ "cc", "libc", @@ -7067,7 +7160,7 @@ dependencies = [ "anyhow", "assets", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "gpui", "language", "languages", @@ -7155,6 +7248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", + "rayon", ] [[package]] @@ -7208,7 +7302,7 @@ name = "media" version = "0.1.0" dependencies = [ "anyhow", - "bindgen 0.70.1", + "bindgen", "core-foundation 0.9.4", "ctor", "foreign-types 0.5.0", @@ -7228,14 +7322,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.35", + "rustix 0.38.40", ] [[package]] name = "memmap2" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] @@ -7303,16 +7397,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", - "simd-adler32", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -7320,6 +7404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -7380,7 +7465,7 @@ dependencies = [ "collections", "ctor", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "gpui", "itertools 0.13.0", "language", @@ -7402,6 +7487,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "naga" version = "22.1.0" @@ -7414,12 +7505,12 @@ dependencies = [ "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "rustc-hash 1.1.0", "spirv", "termcolor", - "thiserror", + "thiserror 1.0.69", "unicode-xid", ] @@ -7469,7 +7560,7 @@ dependencies = [ "jupyter-serde", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "uuid", ] @@ -7484,7 +7575,7 @@ dependencies = [ "log", "ndk-sys", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -7532,7 +7623,7 @@ dependencies = [ "async-trait", "async-watch", "async_zip", - "futures 0.3.30", + "futures 0.3.31", "http_client", "log", "paths", @@ -7818,7 +7909,7 @@ version = "0.8.0-pre" source = "git+https://github.com/KillTheMule/nvim-rs?branch=master#69500bae73b8b3f02a05b7bee621a0d0e633da6c" dependencies = [ "async-trait", - "futures 0.3.30", + "futures 0.3.31", "log", "parity-tokio-ipc", "rmp", @@ -7839,13 +7930,13 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "crc32fast", - "hashbrown 0.14.5", - "indexmap 2.4.0", + "hashbrown 0.15.1", + "indexmap 2.6.0", "memchr", ] @@ -7877,7 +7968,7 @@ name = "ollama" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.30", + "futures 0.3.31", "http_client", "schemars", "serde", @@ -7886,9 +7977,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oo7" @@ -7898,7 +7989,7 @@ checksum = "8fc6ce4692fbfd044ce22ca07dcab1a30fa12432ca2aa5b1294eca50d3332a24" dependencies = [ "aes", "async-fs 2.1.2", - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", "async-net 2.0.0", "blocking", @@ -7906,7 +7997,7 @@ dependencies = [ "cipher", "digest", "endi", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "futures-util", "hkdf", "hmac", @@ -7931,9 +8022,9 @@ checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "open" -version = "5.3.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" +checksum = "3ecd52f0b8d15c40ce4820aa251ed5de032e5d91fab27f7db2f40d42a8bdf69c" dependencies = [ "is-wsl", "libc", @@ -7945,7 +8036,7 @@ name = "open_ai" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.30", + "futures 0.3.31", "http_client", "schemars", "serde", @@ -7967,9 +8058,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -7999,18 +8090,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.3.1+3.3.1" +version = "300.4.0+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" +checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -8196,7 +8287,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" dependencies = [ - "futures 0.3.30", + "futures 0.3.31", "libc", "log", "rand 0.7.3", @@ -8228,7 +8319,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -8322,7 +8413,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "chrono", "pbjson", "pbjson-build", @@ -8388,20 +8479,20 @@ dependencies = [ [[package]] name = "pest" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.69", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -8409,9 +8500,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", @@ -8422,9 +8513,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -8798,7 +8889,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.4.0", + "indexmap 2.6.0", ] [[package]] @@ -8896,18 +8987,18 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", @@ -8916,9 +9007,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -8933,7 +9024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.1.1", + "fastrand 2.2.0", "futures-io", ] @@ -8970,9 +9061,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plist" @@ -8981,7 +9072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ "base64 0.22.1", - "indexmap 2.4.0", + "indexmap 2.6.0", "quick-xml 0.32.0", "serde", "time", @@ -8989,9 +9080,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -9002,30 +9093,30 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "png" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.7.4", + "miniz_oxide", ] [[package]] @@ -9046,15 +9137,15 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.3" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.35", + "rustix 0.38.40", "tracing", "windows-sys 0.59.0", ] @@ -9073,13 +9164,13 @@ checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" dependencies = [ "atomic", "crossbeam-queue", - "futures 0.3.30", + "futures 0.3.31", "log", "parking_lot", "pin-project", "pollster", "static_assertions", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9146,9 +9237,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", "syn 2.0.87", @@ -9160,7 +9251,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.22.20", + "toml_edit 0.22.22", ] [[package]] @@ -9187,6 +9278,28 @@ dependencies = [ "version_check", ] +[[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 2.0.87", +] + [[package]] name = "proc-macro2" version = "1.0.89" @@ -9228,7 +9341,7 @@ dependencies = [ "env_logger 0.11.5", "fancy-regex 0.14.0", "fs", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "git", "git2", @@ -9312,7 +9425,7 @@ version = "0.1.0" dependencies = [ "anyhow", "editor", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "gpui", "language", @@ -9340,7 +9453,7 @@ dependencies = [ "memchr", "parking_lot", "protobuf", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9349,7 +9462,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "prost-derive 0.9.0", ] @@ -9359,7 +9472,7 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "prost-derive 0.12.6", ] @@ -9369,12 +9482,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "heck 0.3.3", "itertools 0.10.5", "lazy_static", "log", - "multimap", + "multimap 0.8.3", "petgraph", "prost 0.9.0", "prost-types 0.9.0", @@ -9389,11 +9502,11 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ - "bytes 1.7.2", - "heck 0.4.1", - "itertools 0.10.5", + "bytes 1.8.0", + "heck 0.5.0", + "itertools 0.12.1", "log", - "multimap", + "multimap 0.10.0", "once_cell", "petgraph", "prettyplease", @@ -9424,7 +9537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.87", @@ -9436,7 +9549,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "prost 0.9.0", ] @@ -9468,9 +9581,9 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "psm" -version = "0.1.21" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" dependencies = [ "cc", ] @@ -9550,9 +9663,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.34.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", ] @@ -9577,45 +9690,49 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash 2.0.0", - "rustls 0.23.13", + "rustls 0.23.16", "socket2 0.5.7", - "thiserror", + "thiserror 2.0.3", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", + "getrandom 0.2.15", "rand 0.8.5", "ring", "rustc-hash 2.0.0", - "rustls 0.23.13", + "rustls 0.23.16", + "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.3", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" dependencies = [ + "cfg_aliases 0.2.1", "libc", "once_cell", "socket2 0.5.7", @@ -9745,22 +9862,23 @@ dependencies = [ "rand_chacha 0.3.1", "simd_helpers", "system-deps", - "thiserror", + "thiserror 1.0.69", "v_frame", "wasm-bindgen", ] [[package]] name = "ravif" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" +checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", + "rayon", "rgb", ] @@ -9804,9 +9922,9 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.20.0" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c141b9980e1150201b2a3a32879001c8f975fe313ec3df5471a9b5c79a880cd" +checksum = "4a04b892cb6f91951f144c33321843790c8574c825aafdb16d815fd7183b5229" dependencies = [ "bytemuck", "font-types", @@ -9820,7 +9938,7 @@ dependencies = [ "auto_update", "editor", "file_finder", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "gpui", "itertools 0.13.0", @@ -9857,18 +9975,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -9881,7 +9990,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9906,14 +10015,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -9927,13 +10036,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -9950,9 +10059,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "release_channel" @@ -9970,7 +10079,7 @@ dependencies = [ "async-trait", "collections", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "itertools 0.13.0", "log", @@ -9984,7 +10093,7 @@ dependencies = [ "shlex", "smol", "tempfile", - "thiserror", + "thiserror 1.0.69", "util", "which 6.0.3", ] @@ -10004,7 +10113,7 @@ dependencies = [ "env_logger 0.11.5", "fork", "fs", - "futures 0.3.30", + "futures 0.3.31", "git", "git_hosting_providers", "gpui", @@ -10058,7 +10167,7 @@ dependencies = [ "editor", "env_logger 0.11.5", "feature_flags", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "image", @@ -10098,7 +10207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", - "bytes 1.7.2", + "bytes 1.8.0", "encoding_rs", "futures-core", "futures-util", @@ -10141,7 +10250,7 @@ version = "0.12.8" source = "git+https://github.com/zed-industries/reqwest.git?rev=fd110f6998da16bbca97b6dddda9be7827c50e29#fd110f6998da16bbca97b6dddda9be7827c50e29" dependencies = [ "base64 0.22.1", - "bytes 1.7.2", + "bytes 1.8.0", "encoding_rs", "futures-core", "futures-util", @@ -10149,7 +10258,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-rustls 0.27.3", "hyper-util", "ipnet", @@ -10160,9 +10269,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.13", + "rustls 0.23.16", "rustls-native-certs 0.8.0", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", @@ -10187,8 +10296,8 @@ name = "reqwest_client" version = "0.1.0" dependencies = [ "anyhow", - "bytes 1.7.2", - "futures 0.3.30", + "bytes 1.8.0", + "futures 0.3.31", "gpui", "http_client", "log", @@ -10225,9 +10334,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.49" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cd5a1e95672f201913966f39baf355b53b5d92833431847295ae0346a5b939" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" dependencies = [ "bytemuck", ] @@ -10236,7 +10345,7 @@ dependencies = [ name = "rich_text" version = "0.1.0" dependencies = [ - "futures 0.3.30", + "futures 0.3.31", "gpui", "language", "linkify", @@ -10269,7 +10378,7 @@ checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", - "bytes 1.7.2", + "bytes 1.8.0", "hashbrown 0.12.3", "ptr_meta", "rend", @@ -10319,7 +10428,7 @@ checksum = "6006a627c1a38d37f3d3a85c6575418cfe34a5392d60a686d0071e1c8d427acb" dependencies = [ "cpal", "hound", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -10356,7 +10465,7 @@ dependencies = [ "chrono", "collections", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "gpui", "parking_lot", "proto", @@ -10401,11 +10510,11 @@ dependencies = [ "async-dispatcher", "async-std", "base64 0.22.1", - "bytes 1.7.2", + "bytes 1.8.0", "chrono", "data-encoding", "dirs 5.0.1", - "futures 0.3.30", + "futures 0.3.31", "glob", "jupyter-serde", "rand 0.8.5", @@ -10461,7 +10570,7 @@ checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", "borsh", - "bytes 1.7.2", + "bytes 1.8.0", "num-traits", "rand 0.8.5", "rkyv", @@ -10512,9 +10621,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags 2.6.0", "errno 0.3.9", @@ -10533,7 +10642,7 @@ checksum = "a25c3aad9fc1424eb82c88087789a7d938e1829724f3e4043163baf0d13cfc12" dependencies = [ "errno 0.3.9", "libc", - "rustix 0.38.35", + "rustix 0.38.40", ] [[package]] @@ -10550,9 +10659,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" dependencies = [ "once_cell", "ring", @@ -10581,7 +10690,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", "security-framework", @@ -10598,19 +10707,21 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" @@ -10635,9 +10746,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "rustybuzz" @@ -10682,11 +10793,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -10756,12 +10867,12 @@ dependencies = [ [[package]] name = "sea-bae" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd3534a9978d0aa7edd2808dc1f8f31c4d0ecd31ddf71d997b3c98e9f3c9114" +checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" dependencies = [ "heck 0.4.1", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.87", @@ -10777,7 +10888,7 @@ dependencies = [ "async-trait", "bigdecimal", "chrono", - "futures 0.3.30", + "futures 0.3.31", "log", "ouroboros", "rust_decimal", @@ -10788,7 +10899,7 @@ dependencies = [ "serde_json", "sqlx", "strum 0.26.3", - "thiserror", + "thiserror 1.0.69", "time", "tracing", "url", @@ -10797,9 +10908,9 @@ dependencies = [ [[package]] name = "sea-orm-macros" -version = "1.1.0-rc.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07aadcb2ee9fad78a3bf74f6430ba94865ab4d8ad237f978e99dafa97ee0df57" +checksum = "3a239e3bb1b566ad4ec2654d0d193d6ceddfd733487edc9c21a64d214c773910" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -10811,13 +10922,12 @@ dependencies = [ [[package]] name = "sea-query" -version = "0.32.0-rc.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fba498acd58ce434669f273505cd07737065472eb541c3f813c7f4ce33993f5" +checksum = "ff504d13b5e4b52fffcf2fb203d0352a5722fa5151696db768933e41e1e591bb" dependencies = [ "bigdecimal", "chrono", - "educe", "inherent", "ordered-float 3.9.2", "rust_decimal", @@ -10828,9 +10938,9 @@ dependencies = [ [[package]] name = "sea-query-binder" -version = "0.7.0-rc.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc3296903e60ddc7c9f4601cd6ef31a4b1584bf22480587e00b9ef743071b57" +checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608" dependencies = [ "bigdecimal", "chrono", @@ -10870,7 +10980,7 @@ dependencies = [ "client", "collections", "editor", - "futures 0.3.30", + "futures 0.3.31", "gpui", "language", "menu", @@ -10915,9 +11025,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -10942,7 +11052,7 @@ dependencies = [ "env_logger 0.11.5", "feature_flags", "fs", - "futures 0.3.30", + "futures 0.3.31", "futures-batch", "gpui", "heed", @@ -10989,18 +11099,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -11044,7 +11154,7 @@ version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "itoa", "memchr", "ryu", @@ -11053,12 +11163,13 @@ dependencies = [ [[package]] name = "serde_json_lenient" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d0bae483150302560d7cb52e7932f39b69a6fbdd099e48d33ef060a8c9c078" +checksum = "2bf0c7e21364d0e199dd2f6c339ca18d6fca75b69458a247e8b27ff1c92f5b86" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "itoa", + "memchr", "ryu", "serde", ] @@ -11081,7 +11192,7 @@ checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -11092,7 +11203,7 @@ checksum = "8cac3f1e2ca2fe333923a1ae72caca910b98ed0630bb35ef6f8c8517d6e81afa" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -11108,9 +11219,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -11146,7 +11257,7 @@ dependencies = [ "collections", "ec4rs", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "indoc", "log", @@ -11303,9 +11414,9 @@ dependencies = [ [[package]] name = "simdutf8" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" @@ -11321,7 +11432,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -11359,9 +11470,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "skrifa" -version = "0.20.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abea4738067b1e628c6ce28b2c216c19e9ea95715cdb332680e821c3bec2ef23" +checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe" dependencies = [ "bytemuck", "read-fonts", @@ -11459,7 +11570,7 @@ dependencies = [ "anyhow", "collections", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "parking_lot", "paths", @@ -11505,9 +11616,9 @@ dependencies = [ [[package]] name = "spdx" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47317bbaf63785b53861e1ae2d11b80d6b624211d42cb20efcd210ee6f8a14bc" +checksum = "bae30cc7bfe3656d60ee99bf6836f472b0c53dddcbf335e253329abb16e535a2" dependencies = [ "smallvec", ] @@ -11562,7 +11673,7 @@ version = "0.1.0" dependencies = [ "anyhow", "collections", - "futures 0.3.30", + "futures 0.3.31", "indoc", "libsqlite3-sys", "parking_lot", @@ -11614,7 +11725,7 @@ dependencies = [ "atoi", "bigdecimal", "byteorder", - "bytes 1.7.2", + "bytes 1.8.0", "chrono", "crc", "crossbeam-queue", @@ -11628,7 +11739,7 @@ dependencies = [ "hashbrown 0.14.5", "hashlink 0.9.1", "hex", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "memchr", "once_cell", @@ -11642,7 +11753,7 @@ dependencies = [ "sha2", "smallvec", "sqlformat", - "thiserror", + "thiserror 1.0.69", "time", "tokio", "tokio-stream", @@ -11702,7 +11813,7 @@ dependencies = [ "bigdecimal", "bitflags 2.6.0", "byteorder", - "bytes 1.7.2", + "bytes 1.8.0", "chrono", "crc", "digest", @@ -11731,7 +11842,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 1.0.69", "time", "tracing", "uuid", @@ -11775,7 +11886,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 1.0.69", "time", "tracing", "uuid", @@ -11965,7 +12076,7 @@ dependencies = [ "collections", "editor", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "language", @@ -11990,7 +12101,7 @@ name = "supermaven_api" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.30", + "futures 0.3.31", "http_client", "paths", "serde", @@ -12000,15 +12111,15 @@ dependencies = [ [[package]] name = "sval" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53eb957fbc79a55306d5d25d87daf3627bc3800681491cda0709eef36c748bfe" +checksum = "f6dc0f9830c49db20e73273ffae9b5240f63c42e515af1da1fceefb69fceafd8" [[package]] name = "sval_buffer" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96e860aef60e9cbf37888d4953a13445abf523c534640d1f6174d310917c410d" +checksum = "429922f7ad43c0ef8fd7309e14d750e38899e32eb7e8da656ea169dd28ee212f" dependencies = [ "sval", "sval_ref", @@ -12016,18 +12127,18 @@ dependencies = [ [[package]] name = "sval_dynamic" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3f2b07929a1127d204ed7cb3905049381708245727680e9139dac317ed556f" +checksum = "68f16ff5d839396c11a30019b659b0976348f3803db0626f736764c473b50ff4" dependencies = [ "sval", ] [[package]] name = "sval_fmt" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e188677497de274a1367c4bda15bd2296de4070d91729aac8f0a09c1abf64d" +checksum = "c01c27a80b6151b0557f9ccbe89c11db571dc5f68113690c1e028d7e974bae94" dependencies = [ "itoa", "ryu", @@ -12036,9 +12147,9 @@ dependencies = [ [[package]] name = "sval_json" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f456c07dae652744781f2245d5e3b78e6a9ebad70790ac11eb15dbdbce5282" +checksum = "0deef63c70da622b2a8069d8600cf4b05396459e665862e7bdb290fd6cf3f155" dependencies = [ "itoa", "ryu", @@ -12047,9 +12158,9 @@ dependencies = [ [[package]] name = "sval_nested" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "886feb24709f0476baaebbf9ac10671a50163caa7e439d7a7beb7f6d81d0a6fb" +checksum = "a39ce5976ae1feb814c35d290cf7cf8cd4f045782fe1548d6bc32e21f6156e9f" dependencies = [ "sval", "sval_buffer", @@ -12058,18 +12169,18 @@ dependencies = [ [[package]] name = "sval_ref" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2e7fc517d778f44f8cb64140afa36010999565528d48985f55e64d45f369ce" +checksum = "bb7c6ee3751795a728bc9316a092023529ffea1783499afbc5c66f5fabebb1fa" dependencies = [ "sval", ] [[package]] name = "sval_serde" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79bf66549a997ff35cd2114a27ac4b0c2843280f2cfa84b240d169ecaa0add46" +checksum = "2a5572d0321b68109a343634e3a5d576bf131b82180c6c442dee06349dfc652a" dependencies = [ "serde", "sval", @@ -12078,9 +12189,9 @@ dependencies = [ [[package]] name = "svg_fmt" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" +checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" [[package]] name = "svgtypes" @@ -12094,9 +12205,9 @@ dependencies = [ [[package]] name = "swash" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93cdc334a50fcc2aa3f04761af3b28196280a6aaadb1ef11215c478ae32615ac" +checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2" dependencies = [ "skrifa", "yazi", @@ -12125,18 +12236,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "sync_wrapper" version = "0.1.2" @@ -12161,6 +12260,17 @@ dependencies = [ "crossbeam-queue", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "sys-locale" version = "0.3.2" @@ -12181,7 +12291,7 @@ dependencies = [ "memchr", "ntapi", "rayon", - "windows 0.54.0", + "windows 0.57.0", ] [[package]] @@ -12250,7 +12360,7 @@ dependencies = [ "cap-std", "fd-lock", "io-lifetimes 2.0.3", - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.52.0", "winx", ] @@ -12315,7 +12425,7 @@ version = "0.1.0" dependencies = [ "anyhow", "collections", - "futures 0.3.30", + "futures 0.3.31", "gpui", "hex", "parking_lot", @@ -12362,14 +12472,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.1.1", + "fastrand 2.2.0", "once_cell", - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.59.0", ] @@ -12401,7 +12511,7 @@ dependencies = [ "anyhow", "collections", "dirs 4.0.0", - "futures 0.3.30", + "futures 0.3.31", "gpui", "libc", "rand 0.8.5", @@ -12415,7 +12525,7 @@ dependencies = [ "sysinfo", "task", "theme", - "thiserror", + "thiserror 1.0.69", "util", "windows 0.58.0", ] @@ -12426,7 +12536,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.59.0", ] @@ -12440,7 +12550,7 @@ dependencies = [ "db", "dirs 4.0.0", "editor", - "futures 0.3.30", + "futures 0.3.31", "gpui", "itertools 0.13.0", "language", @@ -12492,7 +12602,7 @@ dependencies = [ "collections", "derive_more", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "indexmap 1.9.3", "log", @@ -12556,7 +12666,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", ] [[package]] @@ -12570,6 +12689,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -12697,6 +12827,16 @@ dependencies = [ "url", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -12760,12 +12900,12 @@ dependencies = [ [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", - "bytes 1.7.2", + "bytes 1.8.0", "libc", "mio 1.0.2", "parking_lot", @@ -12824,7 +12964,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.13", + "rustls 0.23.16", "rustls-pki-types", "tokio", ] @@ -12838,15 +12978,15 @@ dependencies = [ "either", "futures-io", "futures-util", - "thiserror", + "thiserror 1.0.69", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -12886,7 +13026,7 @@ version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-core", "futures-io", "futures-sink", @@ -12924,7 +13064,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit 0.22.22", ] [[package]] @@ -12942,7 +13082,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", @@ -12951,15 +13091,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -13006,7 +13146,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ "bitflags 1.3.2", - "bytes 1.7.2", + "bytes 1.8.0", "futures-core", "futures-util", "http 0.2.12", @@ -13024,7 +13164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "bitflags 2.6.0", - "bytes 1.7.2", + "bytes 1.8.0", "futures-core", "futures-util", "http 0.2.12", @@ -13125,22 +13265,22 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20f4cd3642c47a85052a887d86704f4eac272969f61b686bdd3f772122aabaff" +checksum = "0203df02a3b6dd63575cc1d6e609edc2181c9a11867a271b25cfd2abff3ec5ca" dependencies = [ "cc", "regex", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "tree-sitter-language", "wasmtime-c-api-impl", ] [[package]] name = "tree-sitter-bash" -version = "0.23.1" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa5e1c6bd02c0053f3f68edcf5d8866b38a8640584279e30fca88149ce14dda" +checksum = "329a4d48623ac337d42b1df84e81a1c9dbb2946907c102ca72db158c1964a52e" dependencies = [ "cc", "tree-sitter-language", @@ -13158,9 +13298,9 @@ dependencies = [ [[package]] name = "tree-sitter-cpp" -version = "0.23.1" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d67e862242878d6ee50e1e5814f267ee3eea0168aea2cdbd700ccfb4c74b6d3" +checksum = "df2196ea9d47b4ab4a31b9297eaa5a5d19a0b121dceb9f118f6790ad0ab94743" dependencies = [ "cc", "tree-sitter-language", @@ -13168,9 +13308,9 @@ dependencies = [ [[package]] name = "tree-sitter-css" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d0018d6b1692a806f9cddaa1e5616951fd58840c39a0b21401b55ab3df12292" +checksum = "25435a275adb3226b6fddab891bbc50d1a500774a44ceb97022a39666ccda75d" dependencies = [ "cc", "tree-sitter-language", @@ -13198,9 +13338,9 @@ dependencies = [ [[package]] name = "tree-sitter-embedded-template" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9644d7586ebe850c84037ee2f4804dda4a9348eef053be6b1e0d7712342a2495" +checksum = "790063ef14e5b67556abc0b3be0ed863fb41d65ee791cf8c0b20eb42a1fa46af" dependencies = [ "cc", "tree-sitter-language", @@ -13208,9 +13348,9 @@ dependencies = [ [[package]] name = "tree-sitter-go" -version = "0.23.1" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf57626e4c9b6d6efaf8a8d5ee1241c5f178ae7bfdf693713ae6a774f01424e" +checksum = "dc4ee804a89f5c0e606b0b20579c86afc7cd0174aebd45c33b6b9c6237bcd97d" dependencies = [ "cc", "tree-sitter-language", @@ -13255,9 +13395,9 @@ dependencies = [ [[package]] name = "tree-sitter-jsdoc" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c4049eb0ad690e34e5f63640f75ce12a2ff8ba18344d0a13926805b139c0c8" +checksum = "a3862dfcb1038fc5e7812d7df14190afdeb7e1415288fd5f51f58395f8cb0faf" dependencies = [ "cc", "tree-sitter-language", @@ -13275,9 +13415,9 @@ dependencies = [ [[package]] name = "tree-sitter-language" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2545046bd1473dac6c626659cc2567c6c0ff302fc8b84a56c4243378276f7f57" +checksum = "e8ddffe35a0e5eeeadf13ff7350af564c6e73993a24db62caee1822b185c2600" [[package]] name = "tree-sitter-md" @@ -13290,9 +13430,9 @@ dependencies = [ [[package]] name = "tree-sitter-python" -version = "0.23.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65661b1a3e24139e2e54207e47d910ab07e28790d78efc7d5dc3a11ce2a110eb" +checksum = "2416de7eea3f2e1bd53c250f2d3f3394fc77f78497680f37f4b87918b8d752e3" dependencies = [ "cc", "tree-sitter-language", @@ -13310,9 +13450,9 @@ dependencies = [ [[package]] name = "tree-sitter-ruby" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ec5ee842e27791e0adffa0b2a177614de51d2a26e5c7e84d014ed7f097e5ed0" +checksum = "be0484ea4ef6bb9c575b4fdabde7e31340a8d2dbc7d52b321ac83da703249f95" dependencies = [ "cc", "tree-sitter-language", @@ -13320,9 +13460,9 @@ dependencies = [ [[package]] name = "tree-sitter-rust" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffbbcb780348fbae8395742ae5b34c1fd794e4085d43aac9f259387f9a84dc8" +checksum = "137ff3de3cc8a98302d048963459ead91135d4a1b423f09d25028b847ec3d3e3" dependencies = [ "cc", "tree-sitter-language", @@ -13330,9 +13470,9 @@ dependencies = [ [[package]] name = "tree-sitter-typescript" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecf1585ae2a9dddc2b1d4c0e2140b2ec9876e2a25fd79de47fcf7dae0384685" +checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff" dependencies = [ "cc", "tree-sitter-language", @@ -13366,7 +13506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ "byteorder", - "bytes 1.7.2", + "bytes 1.8.0", "data-encoding", "http 0.2.12", "httparse", @@ -13374,7 +13514,7 @@ dependencies = [ "rand 0.8.5", "rustls 0.21.12", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] @@ -13386,7 +13526,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" dependencies = [ "byteorder", - "bytes 1.7.2", + "bytes 1.8.0", "data-encoding", "http 1.1.0", "httparse", @@ -13394,7 +13534,7 @@ dependencies = [ "native-tls", "rand 0.8.5", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] @@ -13406,14 +13546,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" dependencies = [ "byteorder", - "bytes 1.7.2", + "bytes 1.8.0", "data-encoding", "http 1.1.0", "httparse", "log", "rand 0.8.5", "sha1", - "thiserror", + "thiserror 1.0.69", "utf-8", ] @@ -13431,9 +13571,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uds_windows" @@ -13487,18 +13627,15 @@ dependencies = [ [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-bidi-mirroring" @@ -13514,9 +13651,9 @@ checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-linebreak" @@ -13526,18 +13663,18 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-script" @@ -13547,21 +13684,21 @@ checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unicode_categories" @@ -13583,9 +13720,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", "idna", @@ -13627,6 +13764,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -13641,7 +13790,7 @@ dependencies = [ "async-fs 1.6.0", "collections", "dirs 4.0.0", - "futures 0.3.30", + "futures 0.3.31", "futures-lite 1.13.0", "git2", "globset", @@ -13659,9 +13808,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom 0.2.15", "serde", @@ -13687,9 +13836,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" dependencies = [ "value-bag-serde1", "value-bag-sval2", @@ -13697,9 +13846,9 @@ dependencies = [ [[package]] name = "value-bag-serde1" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccacf50c5cb077a9abb723c5bcb5e0754c1a433f1e1de89edc328e2760b6328b" +checksum = "4bb773bd36fd59c7ca6e336c94454d9c66386416734817927ac93d81cb3c5b0b" dependencies = [ "erased-serde", "serde", @@ -13708,9 +13857,9 @@ dependencies = [ [[package]] name = "value-bag-sval2" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1785bae486022dfb9703915d42287dcb284c1ee37bd1080eeba78cc04721285b" +checksum = "53a916a702cac43a88694c97657d449775667bcd14b70419441d05b7fea4a83a" dependencies = [ "sval", "sval_buffer", @@ -13765,7 +13914,7 @@ dependencies = [ "command_palette", "command_palette_hooks", "editor", - "futures 0.3.30", + "futures 0.3.31", "gpui", "indoc", "itertools 0.13.0", @@ -13880,7 +14029,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-channel", "futures-util", "headers", @@ -13922,9 +14071,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -13933,9 +14082,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", @@ -13948,9 +14097,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -13960,9 +14109,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -13970,9 +14119,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -13983,9 +14132,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-encoder" @@ -14012,7 +14161,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2" dependencies = [ "anyhow", - "indexmap 2.4.0", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -14023,9 +14172,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -14041,7 +14190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" dependencies = [ "bitflags 2.6.0", - "indexmap 2.4.0", + "indexmap 2.6.0", "semver", ] @@ -14054,7 +14203,7 @@ dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", "hashbrown 0.14.5", - "indexmap 2.4.0", + "indexmap 2.6.0", "semver", "serde", ] @@ -14084,7 +14233,7 @@ dependencies = [ "cfg-if", "encoding_rs", "hashbrown 0.14.5", - "indexmap 2.4.0", + "indexmap 2.6.0", "libc", "libm", "log", @@ -14095,7 +14244,7 @@ dependencies = [ "paste", "postcard", "psm", - "rustix 0.38.35", + "rustix 0.38.40", "semver", "serde", "serde_derive", @@ -14127,9 +14276,9 @@ dependencies = [ [[package]] name = "wasmtime-c-api-impl" -version = "24.0.0" +version = "24.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765e302e7d9125e614aaeec3ad6b6083605393004eca00214106a4ff6b47fc58" +checksum = "4e038dd412700174019867608617127e7cc4f113f764dd10e7488dbf5f47b191" dependencies = [ "anyhow", "log", @@ -14141,9 +14290,9 @@ dependencies = [ [[package]] name = "wasmtime-c-api-macros" -version = "24.0.0" +version = "24.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d09d02eaa84aa2de5babee7b0296557ad6e4903bb10aa8d135e393e753a43d6" +checksum = "bde0ca2263811d980ab676bcb2a190c990737f58969a908976101ad208149a17" dependencies = [ "proc-macro2", "quote", @@ -14188,7 +14337,7 @@ dependencies = [ "log", "object", "target-lexicon", - "thiserror", + "thiserror 1.0.69", "wasmparser 0.215.0", "wasmtime-environ", "wasmtime-versioned-export-macros", @@ -14205,7 +14354,7 @@ dependencies = [ "cranelift-bitset", "cranelift-entity", "gimli 0.29.0", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "object", "postcard", @@ -14230,7 +14379,7 @@ dependencies = [ "anyhow", "cc", "cfg-if", - "rustix 0.38.35", + "rustix 0.38.40", "wasmtime-asm-macros", "wasmtime-versioned-export-macros", "windows-sys 0.52.0", @@ -14281,27 +14430,27 @@ dependencies = [ [[package]] name = "wasmtime-wasi" -version = "24.0.1" +version = "24.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda03f5bfd5c4cc09f75c7e44846663f25f2c48a2d688fbfb5c7a33af6cf34f5" +checksum = "f88f94e393084426f5055d57ce7ae6346ae623783ee6792f411282d6b9e1e5c3" dependencies = [ "anyhow", "async-trait", "bitflags 2.6.0", - "bytes 1.7.2", + "bytes 1.8.0", "cap-fs-ext", "cap-net-ext", "cap-rand", "cap-std", "cap-time-ext", "fs-set-times", - "futures 0.3.30", + "futures 0.3.31", "io-extras", "io-lifetimes 2.0.3", "once_cell", - "rustix 0.38.35", + "rustix 0.38.40", "system-interface", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "url", @@ -14335,7 +14484,7 @@ checksum = "c58b085b2d330e5057dddd31f3ca527569b90fcdd35f6d373420c304927a5190" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.4.0", + "indexmap 2.6.0", "wit-parser 0.215.0", ] @@ -14350,13 +14499,13 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.35", + "rustix 0.38.40", "scoped-tls", "smallvec", "wayland-sys", @@ -14364,23 +14513,23 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" dependencies = [ "bitflags 2.6.0", - "rustix 0.38.35", + "rustix 0.38.40", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-cursor" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95" +checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" dependencies = [ - "rustix 0.38.35", + "rustix 0.38.40", "wayland-client", "xcursor", ] @@ -14412,20 +14561,20 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.4" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", - "quick-xml 0.34.0", + "quick-xml 0.36.2", "quote", ] [[package]] name = "wayland-sys" -version = "0.31.4" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" dependencies = [ "dlib", "log", @@ -14435,9 +14584,19 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +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", @@ -14535,7 +14694,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.35", + "rustix 0.38.40", ] [[package]] @@ -14546,30 +14705,30 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", - "rustix 0.38.35", + "rustix 0.38.40", "winsafe", ] [[package]] name = "whoami" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.4.1", + "redox_syscall 0.5.7", "wasite", ] [[package]] name = "wiggle" -version = "24.0.1" +version = "24.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3b31bd2b4d2d82a4b747b8dbc45f566214214a4ffdc5690429a73bc221dc8a" +checksum = "c72a4c92952216582f55eab27819a1fe8d3c54b292b7b8e5f849b23bfed96e78" dependencies = [ "anyhow", "async-trait", "bitflags 2.6.0", - "thiserror", + "thiserror 1.0.69", "tracing", "wasmtime", "wiggle-macro", @@ -14577,9 +14736,9 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "24.0.1" +version = "24.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2c6136b195fc12067aa9d4e7a5baf118729394df7bc7cbf8c63119bc9f2a7cd" +checksum = "cb744fb938a9fc38207838829b4a43831c1de499e3526eaea71deeff4d9cbb83" dependencies = [ "anyhow", "heck 0.4.1", @@ -14592,9 +14751,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "24.0.1" +version = "24.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a41eaceee468da976ac43b85c4eb82e482f828d5e8e56f49f90dfac2d9bc3b4" +checksum = "7cef395fff17bf8f9c1dee6c0e12801a3ba24928139af0ecb5ccb82ff87bf9d2" dependencies = [ "proc-macro2", "quote", @@ -14660,6 +14819,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[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" @@ -14689,19 +14858,42 @@ dependencies = [ "windows-targets 0.52.6", ] +[[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", - "windows-interface", + "windows-implement 0.58.0", + "windows-interface 0.58.0", "windows-result 0.2.0", "windows-strings", "windows-targets 0.52.6", ] +[[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 2.0.87", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -14713,6 +14905,17 @@ dependencies = [ "syn 2.0.87", ] +[[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 2.0.87", +] + [[package]] name = "windows-interface" version = "0.58.0" @@ -14988,9 +15191,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -15085,7 +15288,7 @@ checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.4.0", + "indexmap 2.6.0", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -15113,7 +15316,7 @@ checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825" dependencies = [ "anyhow", "bitflags 2.6.0", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "serde", "serde_derive", @@ -15132,7 +15335,7 @@ checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" dependencies = [ "anyhow", "id-arena", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "semver", "serde", @@ -15150,7 +15353,7 @@ checksum = "935a97eaffd57c3b413aa510f8f0b550a4a9fe7d59e79cd8b89a83dcb860321f" dependencies = [ "anyhow", "id-arena", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "semver", "serde", @@ -15168,7 +15371,7 @@ checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" dependencies = [ "anyhow", "log", - "thiserror", + "thiserror 1.0.69", "wast", ] @@ -15188,7 +15391,7 @@ dependencies = [ "derive_more", "env_logger 0.11.5", "fs", - "futures 0.3.30", + "futures 0.3.31", "git", "gpui", "http_client", @@ -15225,7 +15428,7 @@ dependencies = [ "collections", "env_logger 0.11.5", "fs", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "git", "git2", @@ -15252,6 +15455,18 @@ dependencies = [ "util", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -15263,9 +15478,9 @@ dependencies = [ [[package]] name = "x11-clipboard" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98785a09322d7446e28a13203d2cae1059a0dd3dfb32cb06d0a225f023d8286" +checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" dependencies = [ "libc", "x11rb", @@ -15280,7 +15495,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "rustix 0.38.35", + "rustix 0.38.40", "x11rb-protocol", ] @@ -15428,6 +15643,30 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + [[package]] name = "zbus" version = "4.4.0" @@ -15437,9 +15676,9 @@ dependencies = [ "async-broadcast", "async-executor", "async-fs 2.1.2", - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", - "async-process 2.2.4", + "async-process 2.3.0", "async-recursion 1.1.1", "async-task", "async-trait", @@ -15528,7 +15767,7 @@ dependencies = [ "file_finder", "file_icons", "fs", - "futures 0.3.30", + "futures 0.3.31", "git", "git_hosting_providers", "go_to_line", @@ -15830,6 +16069,27 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" @@ -15852,15 +16112,15 @@ dependencies = [ [[package]] name = "zeromq" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0560d00172817b7f7c2265060783519c475702ae290b154115ca75e976d4d0" +checksum = "6a4528179201f6eecf211961a7d3276faa61554c82651ecc66387f68fc3004bd" dependencies = [ "async-dispatcher", "async-std", "async-trait", "asynchronous-codec", - "bytes 1.7.2", + "bytes 1.8.0", "crossbeam-queue", "dashmap 5.5.3", "futures-channel", @@ -15873,10 +16133,32 @@ dependencies = [ "parking_lot", "rand 0.8.5", "regex", - "thiserror", + "thiserror 1.0.69", "uuid", ] +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "zip" version = "0.6.6" diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 1a6e1c217e..b682bfdcca 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -3333,7 +3333,8 @@ impl ContextEditor { self.context.update(cx, |context, cx| { for image in images { - let Some(render_image) = image.to_image_data(cx).log_err() else { + let Some(render_image) = image.to_image_data(cx.svg_renderer()).log_err() + else { continue; }; let image_id = image.id(); diff --git a/crates/gpui/examples/gif_viewer.rs b/crates/gpui/examples/gif_viewer.rs index 455a7d6ba9..da175e8a69 100644 --- a/crates/gpui/examples/gif_viewer.rs +++ b/crates/gpui/examples/gif_viewer.rs @@ -1,6 +1,4 @@ -use gpui::{ - div, img, prelude::*, App, AppContext, ImageSource, Render, ViewContext, WindowOptions, -}; +use gpui::{div, img, prelude::*, App, AppContext, Render, ViewContext, WindowOptions}; use std::path::PathBuf; struct GifViewer { @@ -16,7 +14,7 @@ impl GifViewer { impl Render for GifViewer { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { div().size_full().child( - img(ImageSource::File(self.gif_path.clone().into())) + img(self.gif_path.clone()) .size_full() .object_fit(gpui::ObjectFit::Contain) .id("gif"), diff --git a/crates/gpui/examples/image/image.rs b/crates/gpui/examples/image/image.rs index 24a94bf746..19a4e9313f 100644 --- a/crates/gpui/examples/image/image.rs +++ b/crates/gpui/examples/image/image.rs @@ -61,7 +61,7 @@ impl RenderOnce for ImageContainer { } struct ImageShowcase { - local_resource: Arc, + local_resource: Arc, remote_resource: SharedUri, asset_resource: SharedString, } @@ -153,9 +153,10 @@ fn main() { cx.open_window(window_options, |cx| { cx.new_view(|_cx| ImageShowcase { // Relative path to your root project path - local_resource: Arc::new( - PathBuf::from_str("crates/gpui/examples/image/app-icon.png").unwrap(), - ), + local_resource: PathBuf::from_str("crates/gpui/examples/image/app-icon.png") + .unwrap() + .into(), + remote_resource: "https://picsum.photos/512/512".into(), asset_resource: "image/color.svg".into(), diff --git a/crates/gpui/examples/image_loading.rs b/crates/gpui/examples/image_loading.rs new file mode 100644 index 0000000000..eef7d03e43 --- /dev/null +++ b/crates/gpui/examples/image_loading.rs @@ -0,0 +1,214 @@ +use std::{path::Path, sync::Arc, time::Duration}; + +use anyhow::anyhow; +use gpui::{ + black, div, img, prelude::*, pulsating_between, px, red, size, Animation, AnimationExt, App, + AppContext, Asset, AssetLogger, AssetSource, Bounds, Hsla, ImageAssetLoader, ImageCacheError, + ImgResourceLoader, Length, Pixels, RenderImage, Resource, SharedString, ViewContext, + WindowBounds, WindowContext, WindowOptions, LOADING_DELAY, +}; + +struct Assets {} + +impl AssetSource for Assets { + fn load(&self, path: &str) -> anyhow::Result>> { + std::fs::read(path) + .map(Into::into) + .map_err(Into::into) + .map(Some) + } + + fn list(&self, path: &str) -> anyhow::Result> { + Ok(std::fs::read_dir(path)? + .filter_map(|entry| { + Some(SharedString::from( + entry.ok()?.path().to_string_lossy().to_string(), + )) + }) + .collect::>()) + } +} + +const IMAGE: &str = "examples/image/app-icon.png"; + +#[derive(Copy, Clone, Hash)] +struct LoadImageParameters { + timeout: Duration, + fail: bool, +} + +struct LoadImageWithParameters {} + +impl Asset for LoadImageWithParameters { + type Source = LoadImageParameters; + + type Output = Result, ImageCacheError>; + + fn load( + parameters: Self::Source, + cx: &mut AppContext, + ) -> impl std::future::Future + Send + 'static { + let timer = cx.background_executor().timer(parameters.timeout); + let data = AssetLogger::::load( + Resource::Path(Path::new(IMAGE).to_path_buf().into()), + cx, + ); + async move { + timer.await; + if parameters.fail { + log::error!("Intentionally failed to load image"); + Err(anyhow!("Failed to load image").into()) + } else { + data.await + } + } + } +} + +struct ImageLoadingExample {} + +impl ImageLoadingExample { + fn loading_element() -> impl IntoElement { + div().size_full().flex_none().p_0p5().rounded_sm().child( + div().size_full().with_animation( + "loading-bg", + Animation::new(Duration::from_secs(3)) + .repeat() + .with_easing(pulsating_between(0.04, 0.24)), + move |this, delta| this.bg(black().opacity(delta)), + ), + ) + } + + fn fallback_element() -> impl IntoElement { + let fallback_color: Hsla = black().opacity(0.5); + + div().size_full().flex_none().p_0p5().child( + div() + .size_full() + .flex() + .items_center() + .justify_center() + .rounded_sm() + .text_sm() + .text_color(fallback_color) + .border_1() + .border_color(fallback_color) + .child("?"), + ) + } +} + +impl Render for ImageLoadingExample { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + div().flex().flex_col().size_full().justify_around().child( + div().flex().flex_row().w_full().justify_around().child( + div() + .flex() + .bg(gpui::white()) + .size(Length::Definite(Pixels(300.0).into())) + .justify_center() + .items_center() + .child({ + let image_source = LoadImageParameters { + timeout: LOADING_DELAY.saturating_sub(Duration::from_millis(25)), + fail: false, + }; + + // Load within the 'loading delay', should not show loading fallback + img(move |cx: &mut WindowContext| { + cx.use_asset::(&image_source) + }) + .id("image-1") + .border_1() + .size_12() + .with_fallback(|| Self::fallback_element().into_any_element()) + .border_color(red()) + .with_loading(|| Self::loading_element().into_any_element()) + .on_click(move |_, cx| { + cx.remove_asset::(&image_source); + }) + }) + .child({ + // Load after a long delay + let image_source = LoadImageParameters { + timeout: Duration::from_secs(5), + fail: false, + }; + + img(move |cx: &mut WindowContext| { + cx.use_asset::(&image_source) + }) + .id("image-2") + .with_fallback(|| Self::fallback_element().into_any_element()) + .with_loading(|| Self::loading_element().into_any_element()) + .size_12() + .border_1() + .border_color(red()) + .on_click(move |_, cx| { + cx.remove_asset::(&image_source); + }) + }) + .child({ + // Fail to load image after a long delay + let image_source = LoadImageParameters { + timeout: Duration::from_secs(5), + fail: true, + }; + + // Fail to load after a long delay + img(move |cx: &mut WindowContext| { + cx.use_asset::(&image_source) + }) + .id("image-3") + .with_fallback(|| Self::fallback_element().into_any_element()) + .with_loading(|| Self::loading_element().into_any_element()) + .size_12() + .border_1() + .border_color(red()) + .on_click(move |_, cx| { + cx.remove_asset::(&image_source); + }) + }) + .child({ + // Ensure that the normal image loader doesn't spam logs + let image_source = Path::new( + "this/file/really/shouldn't/exist/or/won't/be/an/image/I/hope", + ) + .to_path_buf(); + img(image_source.clone()) + .id("image-1") + .border_1() + .size_12() + .with_fallback(|| Self::fallback_element().into_any_element()) + .border_color(red()) + .with_loading(|| Self::loading_element().into_any_element()) + .on_click(move |_, cx| { + cx.remove_asset::(&image_source.clone().into()); + }) + }), + ), + ) + } +} + +fn main() { + env_logger::init(); + App::new() + .with_assets(Assets {}) + .run(|cx: &mut AppContext| { + let options = WindowOptions { + window_bounds: Some(WindowBounds::Windowed(Bounds::centered( + None, + size(px(300.), Pixels(300.)), + cx, + ))), + ..Default::default() + }; + cx.open_window(options, |cx| { + cx.activate(false); + cx.new_view(|_cx| ImageLoadingExample {}) + }) + .unwrap(); + }); +} diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index da14927fa2..50cadeff8f 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -747,7 +747,7 @@ impl AppContext { } /// Returns the SVG renderer GPUI uses - pub(crate) fn svg_renderer(&self) -> SvgRenderer { + pub fn svg_renderer(&self) -> SvgRenderer { self.svg_renderer.clone() } @@ -1369,7 +1369,7 @@ impl AppContext { } /// Remove an asset from GPUI's cache - pub fn remove_cached_asset(&mut self, source: &A::Source) { + pub fn remove_asset(&mut self, source: &A::Source) { let asset_id = (TypeId::of::(), hash(source)); self.loading_assets.remove(&asset_id); } @@ -1378,12 +1378,7 @@ impl AppContext { /// /// Note that the multiple calls to this method will only result in one `Asset::load` call at a /// time, and the results of this call will be cached - /// - /// This asset will not be cached by default, see [Self::use_cached_asset] - pub fn fetch_asset( - &mut self, - source: &A::Source, - ) -> (Shared>, bool) { + pub fn fetch_asset(&mut self, source: &A::Source) -> (Shared>, bool) { let asset_id = (TypeId::of::(), hash(source)); let mut is_first = false; let task = self diff --git a/crates/gpui/src/asset_cache.rs b/crates/gpui/src/asset_cache.rs index 0c6e5f2f90..57ac1fb582 100644 --- a/crates/gpui/src/asset_cache.rs +++ b/crates/gpui/src/asset_cache.rs @@ -1,30 +1,43 @@ use crate::{AppContext, SharedString, SharedUri}; use futures::Future; + +use std::fmt::Debug; use std::hash::{Hash, Hasher}; -use std::path::PathBuf; +use std::marker::PhantomData; +use std::path::{Path, PathBuf}; use std::sync::Arc; +/// An enum representing #[derive(Debug, PartialEq, Eq, Hash, Clone)] -pub(crate) enum UriOrPath { +pub enum Resource { + /// This resource is at a given URI Uri(SharedUri), - Path(Arc), + /// This resource is at a given path in the file system + Path(Arc), + /// This resource is embedded in the application binary Embedded(SharedString), } -impl From for UriOrPath { +impl From for Resource { fn from(value: SharedUri) -> Self { Self::Uri(value) } } -impl From> for UriOrPath { - fn from(value: Arc) -> Self { +impl From for Resource { + fn from(value: PathBuf) -> Self { + Self::Path(value.into()) + } +} + +impl From> for Resource { + fn from(value: Arc) -> Self { Self::Path(value) } } /// A trait for asynchronous asset loading. -pub trait Asset { +pub trait Asset: 'static { /// The source of the asset. type Source: Clone + Hash + Send; @@ -38,6 +51,31 @@ pub trait Asset { ) -> impl Future + Send + 'static; } +/// An asset Loader that logs whatever passes through it +pub enum AssetLogger { + #[doc(hidden)] + _Phantom(PhantomData, &'static dyn crate::seal::Sealed), +} + +impl>> Asset + for AssetLogger +{ + type Source = T::Source; + + type Output = T::Output; + + fn load( + source: Self::Source, + cx: &mut AppContext, + ) -> impl Future + Send + 'static { + let load = T::load(source, cx); + async { + load.await + .inspect_err(|e| log::error!("Failed to load asset: {}", e)) + } + } +} + /// Use a quick, non-cryptographically secure hash function to get an identifier from data pub fn hash(data: &T) -> u64 { let mut hasher = collections::FxHasher::default(); diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 961e28364f..6928ca74ee 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -2383,7 +2383,7 @@ where /// A wrapper around an element that can store state, produced after assigning an ElementId. pub struct Stateful { - element: E, + pub(crate) element: E, } impl Styled for Stateful diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 729e944b34..4e32672095 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -1,9 +1,11 @@ use crate::{ - px, AbsoluteLength, AppContext, Asset, Bounds, DefiniteLength, Element, ElementId, - GlobalElementId, Hitbox, Image, InteractiveElement, Interactivity, IntoElement, LayoutId, - Length, ObjectFit, Pixels, RenderImage, SharedString, SharedUri, StyleRefinement, Styled, - SvgSize, UriOrPath, WindowContext, + px, AbsoluteLength, AnyElement, AppContext, Asset, AssetLogger, Bounds, DefiniteLength, + Element, ElementId, GlobalElementId, Hitbox, Image, InteractiveElement, Interactivity, + IntoElement, LayoutId, Length, ObjectFit, Pixels, RenderImage, Resource, SharedString, + SharedUri, StyleRefinement, Styled, SvgSize, Task, WindowContext, }; +use anyhow::{anyhow, Result}; + use futures::{AsyncReadExt, Future}; use image::{ codecs::gif::GifDecoder, AnimationDecoder, Frame, ImageBuffer, ImageError, ImageFormat, @@ -11,45 +13,56 @@ use image::{ use smallvec::SmallVec; use std::{ fs, - io::Cursor, - path::PathBuf, + io::{self, Cursor}, + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, + str::FromStr, sync::Arc, time::{Duration, Instant}, }; use thiserror::Error; use util::ResultExt; +use super::{FocusableElement, Stateful, StatefulInteractiveElement}; + +/// The delay before showing the loading state. +pub const LOADING_DELAY: Duration = Duration::from_millis(200); + +/// A type alias to the resource loader that the `img()` element uses. +/// +/// Note: that this is only for Resources, like URLs or file paths. +/// Custom loaders, or external images will not use this asset loader +pub type ImgResourceLoader = AssetLogger; + /// A source of image content. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone)] pub enum ImageSource { - /// Image content will be loaded from provided URI at render time. - Uri(SharedUri), - /// Image content will be loaded from the provided file at render time. - File(Arc), + /// The image content will be loaded from some resource location + Resource(Resource), /// Cached image data Render(Arc), /// Cached image data Image(Arc), - /// Image content will be loaded from Asset at render time. - Embedded(SharedString), + /// A custom loading function to use + Custom(Arc Option, ImageCacheError>>>), } fn is_uri(uri: &str) -> bool { - uri.contains("://") + http_client::Uri::from_str(uri).is_ok() } impl From for ImageSource { fn from(value: SharedUri) -> Self { - Self::Uri(value) + Self::Resource(Resource::Uri(value)) } } -impl From<&'static str> for ImageSource { - fn from(s: &'static str) -> Self { +impl<'a> From<&'a str> for ImageSource { + fn from(s: &'a str) -> Self { if is_uri(s) { - Self::Uri(s.into()) + Self::Resource(Resource::Uri(s.to_string().into())) } else { - Self::Embedded(s.into()) + Self::Resource(Resource::Embedded(s.to_string().into())) } } } @@ -57,32 +70,34 @@ impl From<&'static str> for ImageSource { impl From for ImageSource { fn from(s: String) -> Self { if is_uri(&s) { - Self::Uri(s.into()) + Self::Resource(Resource::Uri(s.into())) } else { - Self::Embedded(s.into()) + Self::Resource(Resource::Embedded(s.into())) } } } impl From for ImageSource { fn from(s: SharedString) -> Self { - if is_uri(&s) { - Self::Uri(s.into()) - } else { - Self::Embedded(s) - } + s.as_ref().into() } } -impl From> for ImageSource { - fn from(value: Arc) -> Self { - Self::File(value) +impl From<&Path> for ImageSource { + fn from(value: &Path) -> Self { + Self::Resource(value.to_path_buf().into()) + } +} + +impl From> for ImageSource { + fn from(value: Arc) -> Self { + Self::Resource(value.into()) } } impl From for ImageSource { fn from(value: PathBuf) -> Self { - Self::File(value.into()) + Self::Resource(value.into()) } } @@ -98,12 +113,80 @@ impl From> for ImageSource { } } +impl Option, ImageCacheError>> + 'static> + From for ImageSource +{ + fn from(value: F) -> Self { + Self::Custom(Arc::new(value)) + } +} + +/// The style of an image element. +pub struct ImageStyle { + grayscale: bool, + object_fit: ObjectFit, + loading: Option AnyElement>>, + fallback: Option AnyElement>>, +} + +impl Default for ImageStyle { + fn default() -> Self { + Self { + grayscale: false, + object_fit: ObjectFit::Contain, + loading: None, + fallback: None, + } + } +} + +/// Style an image element. +pub trait StyledImage: Sized { + /// Get a mutable [ImageStyle] from the element. + fn image_style(&mut self) -> &mut ImageStyle; + + /// Set the image to be displayed in grayscale. + fn grayscale(mut self, grayscale: bool) -> Self { + self.image_style().grayscale = grayscale; + self + } + + /// Set the object fit for the image. + fn object_fit(mut self, object_fit: ObjectFit) -> Self { + self.image_style().object_fit = object_fit; + self + } + + /// Set the object fit for the image. + fn with_fallback(mut self, fallback: impl Fn() -> AnyElement + 'static) -> Self { + self.image_style().fallback = Some(Box::new(fallback)); + self + } + + /// Set the object fit for the image. + fn with_loading(mut self, loading: impl Fn() -> AnyElement + 'static) -> Self { + self.image_style().loading = Some(Box::new(loading)); + self + } +} + +impl StyledImage for Img { + fn image_style(&mut self) -> &mut ImageStyle { + &mut self.style + } +} + +impl StyledImage for Stateful { + fn image_style(&mut self) -> &mut ImageStyle { + &mut self.element.style + } +} + /// An image element. pub struct Img { interactivity: Interactivity, source: ImageSource, - grayscale: bool, - object_fit: ObjectFit, + style: ImageStyle, } /// Create a new image element. @@ -111,8 +194,7 @@ pub fn img(source: impl Into) -> Img { Img { interactivity: Interactivity::default(), source: source.into(), - grayscale: false, - object_fit: ObjectFit::Contain, + style: ImageStyle::default(), } } @@ -125,16 +207,19 @@ impl Img { "hdr", "exr", "pbm", "pam", "ppm", "pgm", "ff", "farbfeld", "qoi", "svg", ] } +} - /// Set the image to be displayed in grayscale. - pub fn grayscale(mut self, grayscale: bool) -> Self { - self.grayscale = grayscale; - self +impl Deref for Stateful { + type Target = Img; + + fn deref(&self) -> &Self::Target { + &self.element } - /// Set the object fit for the image. - pub fn object_fit(mut self, object_fit: ObjectFit) -> Self { - self.object_fit = object_fit; - self +} + +impl DerefMut for Stateful { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.element } } @@ -142,10 +227,17 @@ impl Img { struct ImgState { frame_index: usize, last_frame_time: Option, + started_loading: Option<(Instant, Task<()>)>, +} + +/// The image layout state between frames +pub struct ImgLayoutState { + frame_index: usize, + replacement: Option, } impl Element for Img { - type RequestLayoutState = usize; + type RequestLayoutState = ImgLayoutState; type PrepaintState = Option; fn id(&self) -> Option { @@ -157,11 +249,17 @@ impl Element for Img { global_id: Option<&GlobalElementId>, cx: &mut WindowContext, ) -> (LayoutId, Self::RequestLayoutState) { + let mut layout_state = ImgLayoutState { + frame_index: 0, + replacement: None, + }; + cx.with_optional_element_state(global_id, |state, cx| { let mut state = state.map(|state| { state.unwrap_or(ImgState { frame_index: 0, last_frame_time: None, + started_loading: None, }) }); @@ -170,64 +268,105 @@ impl Element for Img { let layout_id = self .interactivity .request_layout(global_id, cx, |mut style, cx| { - if let Some(data) = self.source.use_data(cx) { - if let Some(state) = &mut state { - let frame_count = data.frame_count(); - if frame_count > 1 { - let current_time = Instant::now(); - if let Some(last_frame_time) = state.last_frame_time { - let elapsed = current_time - last_frame_time; - let frame_duration = - Duration::from(data.delay(state.frame_index)); + let mut replacement_id = None; - if elapsed >= frame_duration { - state.frame_index = (state.frame_index + 1) % frame_count; - state.last_frame_time = - Some(current_time - (elapsed - frame_duration)); + match self.source.use_data(cx) { + Some(Ok(data)) => { + if let Some(state) = &mut state { + let frame_count = data.frame_count(); + if frame_count > 1 { + let current_time = Instant::now(); + if let Some(last_frame_time) = state.last_frame_time { + let elapsed = current_time - last_frame_time; + let frame_duration = + Duration::from(data.delay(state.frame_index)); + + if elapsed >= frame_duration { + state.frame_index = + (state.frame_index + 1) % frame_count; + state.last_frame_time = + Some(current_time - (elapsed - frame_duration)); + } + } else { + state.last_frame_time = Some(current_time); + } + } + state.started_loading = None; + } + + let image_size = data.size(frame_index); + + if let Length::Auto = style.size.width { + style.size.width = match style.size.height { + Length::Definite(DefiniteLength::Absolute( + AbsoluteLength::Pixels(height), + )) => Length::Definite( + px(image_size.width.0 as f32 * height.0 + / image_size.height.0 as f32) + .into(), + ), + _ => Length::Definite(px(image_size.width.0 as f32).into()), + }; + } + + if let Length::Auto = style.size.height { + style.size.height = match style.size.width { + Length::Definite(DefiniteLength::Absolute( + AbsoluteLength::Pixels(width), + )) => Length::Definite( + px(image_size.height.0 as f32 * width.0 + / image_size.width.0 as f32) + .into(), + ), + _ => Length::Definite(px(image_size.height.0 as f32).into()), + }; + } + + if global_id.is_some() && data.frame_count() > 1 { + cx.request_animation_frame(); + } + } + Some(_err) => { + if let Some(fallback) = self.style.fallback.as_ref() { + let mut element = fallback(); + replacement_id = Some(element.request_layout(cx)); + layout_state.replacement = Some(element); + } + if let Some(state) = &mut state { + state.started_loading = None; + } + } + None => { + if let Some(state) = &mut state { + if let Some((started_loading, _)) = state.started_loading { + if started_loading.elapsed() > LOADING_DELAY { + if let Some(loading) = self.style.loading.as_ref() { + let mut element = loading(); + replacement_id = Some(element.request_layout(cx)); + layout_state.replacement = Some(element); + } } } else { - state.last_frame_time = Some(current_time); + let parent_view_id = cx.parent_view_id(); + let task = cx.spawn(|mut cx| async move { + cx.background_executor().timer(LOADING_DELAY).await; + cx.update(|cx| { + cx.notify(parent_view_id); + }) + .ok(); + }); + state.started_loading = Some((Instant::now(), task)); } } } - - let image_size = data.size(frame_index); - - if let Length::Auto = style.size.width { - style.size.width = match style.size.height { - Length::Definite(DefiniteLength::Absolute( - AbsoluteLength::Pixels(height), - )) => Length::Definite( - px(image_size.width.0 as f32 * height.0 - / image_size.height.0 as f32) - .into(), - ), - _ => Length::Definite(px(image_size.width.0 as f32).into()), - }; - } - - if let Length::Auto = style.size.height { - style.size.height = match style.size.width { - Length::Definite(DefiniteLength::Absolute( - AbsoluteLength::Pixels(width), - )) => Length::Definite( - px(image_size.height.0 as f32 * width.0 - / image_size.width.0 as f32) - .into(), - ), - _ => Length::Definite(px(image_size.height.0 as f32).into()), - }; - } - - if global_id.is_some() && data.frame_count() > 1 { - cx.request_animation_frame(); - } } - cx.request_layout(style, []) + cx.request_layout(style, replacement_id) }); - ((layout_id, frame_index), state) + layout_state.frame_index = frame_index; + + ((layout_id, layout_state), state) }) } @@ -235,18 +374,24 @@ impl Element for Img { &mut self, global_id: Option<&GlobalElementId>, bounds: Bounds, - _request_layout: &mut Self::RequestLayoutState, + request_layout: &mut Self::RequestLayoutState, cx: &mut WindowContext, - ) -> Option { + ) -> Self::PrepaintState { self.interactivity - .prepaint(global_id, bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) + .prepaint(global_id, bounds, bounds.size, cx, |_, _, hitbox, cx| { + if let Some(replacement) = &mut request_layout.replacement { + replacement.prepaint(cx); + } + + hitbox + }) } fn paint( &mut self, global_id: Option<&GlobalElementId>, bounds: Bounds, - frame_index: &mut Self::RequestLayoutState, + layout_state: &mut Self::RequestLayoutState, hitbox: &mut Self::PrepaintState, cx: &mut WindowContext, ) { @@ -255,29 +400,26 @@ impl Element for Img { .paint(global_id, bounds, hitbox.as_ref(), cx, |style, cx| { let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size()); - if let Some(data) = source.use_data(cx) { - let new_bounds = self.object_fit.get_bounds(bounds, data.size(*frame_index)); + if let Some(Ok(data)) = source.use_data(cx) { + let new_bounds = self + .style + .object_fit + .get_bounds(bounds, data.size(layout_state.frame_index)); cx.paint_image( new_bounds, corner_radii, data.clone(), - *frame_index, - self.grayscale, + layout_state.frame_index, + self.style.grayscale, ) .log_err(); + } else if let Some(replacement) = &mut layout_state.replacement { + replacement.paint(cx); } }) } } -impl IntoElement for Img { - type Element = Self; - - fn into_element(self) -> Self::Element { - self - } -} - impl Styled for Img { fn style(&mut self) -> &mut StyleRefinement { &mut self.interactivity.base_style @@ -290,41 +432,28 @@ impl InteractiveElement for Img { } } -impl ImageSource { - pub(crate) fn use_data(&self, cx: &mut WindowContext) -> Option> { - match self { - ImageSource::Uri(_) | ImageSource::Embedded(_) | ImageSource::File(_) => { - let uri_or_path: UriOrPath = match self { - ImageSource::Uri(uri) => uri.clone().into(), - ImageSource::File(path) => path.clone().into(), - ImageSource::Embedded(path) => UriOrPath::Embedded(path.clone()), - _ => unreachable!(), - }; +impl IntoElement for Img { + type Element = Self; - cx.use_asset::(&uri_or_path)?.log_err() - } - - ImageSource::Render(data) => Some(data.to_owned()), - ImageSource::Image(data) => cx.use_asset::(data)?.log_err(), - } + fn into_element(self) -> Self::Element { + self } +} - /// Fetch the data associated with this source, using GPUI's asset caching - pub async fn data(&self, cx: &mut AppContext) -> Option> { +impl FocusableElement for Img {} + +impl StatefulInteractiveElement for Img {} + +impl ImageSource { + pub(crate) fn use_data( + &self, + cx: &mut WindowContext, + ) -> Option, ImageCacheError>> { match self { - ImageSource::Uri(_) | ImageSource::Embedded(_) | ImageSource::File(_) => { - let uri_or_path: UriOrPath = match self { - ImageSource::Uri(uri) => uri.clone().into(), - ImageSource::File(path) => path.clone().into(), - ImageSource::Embedded(path) => UriOrPath::Embedded(path.clone()), - _ => unreachable!(), - }; - - cx.fetch_asset::(&uri_or_path).0.await.log_err() - } - - ImageSource::Render(data) => Some(data.to_owned()), - ImageSource::Image(data) => cx.fetch_asset::(data).0.await.log_err(), + ImageSource::Resource(resource) => cx.use_asset::(&resource), + ImageSource::Custom(loading_fn) => loading_fn(cx), + ImageSource::Render(data) => Some(Ok(data.to_owned())), + ImageSource::Image(data) => cx.use_asset::>(data), } } } @@ -334,22 +463,23 @@ enum ImageDecoder {} impl Asset for ImageDecoder { type Source = Arc; - type Output = Result, Arc>; + type Output = Result, ImageCacheError>; fn load( source: Self::Source, cx: &mut AppContext, ) -> impl Future + Send + 'static { - let result = source.to_image_data(cx).map_err(Arc::new); - async { result } + let renderer = cx.svg_renderer(); + async move { source.to_image_data(renderer).map_err(Into::into) } } } +/// An image loader for the GPUI asset system #[derive(Clone)] -enum ImageAsset {} +pub enum ImageAssetLoader {} -impl Asset for ImageAsset { - type Source = UriOrPath; +impl Asset for ImageAssetLoader { + type Source = Resource; type Output = Result, ImageCacheError>; fn load( @@ -363,12 +493,12 @@ impl Asset for ImageAsset { let asset_source = cx.asset_source().clone(); async move { let bytes = match source.clone() { - UriOrPath::Path(uri) => fs::read(uri.as_ref())?, - UriOrPath::Uri(uri) => { + Resource::Path(uri) => fs::read(uri.as_ref())?, + Resource::Uri(uri) => { let mut response = client .get(uri.as_ref(), ().into(), true) .await - .map_err(|e| ImageCacheError::Client(Arc::new(e)))?; + .map_err(|e| anyhow!(e))?; let mut body = Vec::new(); response.body_mut().read_to_end(&mut body).await?; if !response.status().is_success() { @@ -383,13 +513,13 @@ impl Asset for ImageAsset { } body } - UriOrPath::Embedded(path) => { + Resource::Embedded(path) => { let data = asset_source.load(&path).ok().flatten(); if let Some(data) = data { data.to_vec() } else { return Err(ImageCacheError::Asset( - format!("not found: {}", path).into(), + format!("Embedded resource not found: {}", path).into(), )); } } @@ -450,9 +580,9 @@ impl Asset for ImageAsset { /// An error that can occur when interacting with the image cache. #[derive(Debug, Error, Clone)] pub enum ImageCacheError { - /// An error that occurred while fetching an image from a remote source. - #[error("http error: {0}")] - Client(#[from] Arc), + /// Some other kind of error occurred + #[error("error: {0}")] + Other(#[from] Arc), /// An error that occurred while reading the image from disk. #[error("IO error: {0}")] Io(Arc), @@ -477,20 +607,26 @@ pub enum ImageCacheError { Usvg(Arc), } -impl From for ImageCacheError { - fn from(error: std::io::Error) -> Self { - Self::Io(Arc::new(error)) +impl From for ImageCacheError { + fn from(value: anyhow::Error) -> Self { + Self::Other(Arc::new(value)) } } -impl From for ImageCacheError { - fn from(error: ImageError) -> Self { - Self::Image(Arc::new(error)) +impl From for ImageCacheError { + fn from(value: io::Error) -> Self { + Self::Io(Arc::new(value)) } } impl From for ImageCacheError { - fn from(error: usvg::Error) -> Self { - Self::Usvg(Arc::new(error)) + fn from(value: usvg::Error) -> Self { + Self::Usvg(Arc::new(value)) + } +} + +impl From for ImageCacheError { + fn from(value: image::ImageError) -> Self { + Self::Image(Arc::new(value)) } } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 727ca952da..3e4d0706f0 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -27,11 +27,11 @@ mod test; mod windows; use crate::{ - point, Action, AnyWindowHandle, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, - DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, - GPUSpecs, GlyphId, ImageSource, Keymap, LineLayout, Pixels, PlatformInput, Point, - RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, ScaledPixels, Scene, - SharedString, Size, SvgSize, Task, TaskLabel, WindowContext, DEFAULT_WINDOW_SIZE, + point, Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, + DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GPUSpecs, GlyphId, + ImageSource, Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImage, + RenderImageParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, SvgRenderer, + SvgSize, Task, TaskLabel, WindowContext, DEFAULT_WINDOW_SIZE, }; use anyhow::{anyhow, Result}; use async_task::Runnable; @@ -1290,11 +1290,13 @@ impl Image { /// Use the GPUI `use_asset` API to make this image renderable pub fn use_render_image(self: Arc, cx: &mut WindowContext) -> Option> { - ImageSource::Image(self).use_data(cx) + ImageSource::Image(self) + .use_data(cx) + .and_then(|result| result.ok()) } /// Convert the clipboard image to an `ImageData` object. - pub fn to_image_data(&self, cx: &AppContext) -> Result> { + pub fn to_image_data(&self, svg_renderer: SvgRenderer) -> Result> { fn frames_for_image( bytes: &[u8], format: image::ImageFormat, @@ -1331,10 +1333,7 @@ impl Image { ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?, ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?, ImageFormat::Svg => { - // TODO: Fix this - let pixmap = cx - .svg_renderer() - .render_pixmap(&self.bytes, SvgSize::ScaleFactor(1.0))?; + let pixmap = svg_renderer.render_pixmap(&self.bytes, SvgSize::ScaleFactor(1.0))?; let buffer = image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take()) diff --git a/crates/gpui/src/prelude.rs b/crates/gpui/src/prelude.rs index 2ab115fa62..e1cc14e93e 100644 --- a/crates/gpui/src/prelude.rs +++ b/crates/gpui/src/prelude.rs @@ -5,5 +5,5 @@ pub use crate::{ util::FluentBuilder, BorrowAppContext, BorrowWindow, Context, Element, FocusableElement, InteractiveElement, IntoElement, ParentElement, Refineable, Render, RenderOnce, - StatefulInteractiveElement, Styled, VisualContext, + StatefulInteractiveElement, Styled, StyledImage, VisualContext, }; diff --git a/crates/gpui/src/svg_renderer.rs b/crates/gpui/src/svg_renderer.rs index a5cbb67372..f99880ec5e 100644 --- a/crates/gpui/src/svg_renderer.rs +++ b/crates/gpui/src/svg_renderer.rs @@ -10,7 +10,7 @@ pub(crate) struct RenderSvgParams { } #[derive(Clone)] -pub(crate) struct SvgRenderer { +pub struct SvgRenderer { asset_source: Arc, } @@ -24,7 +24,7 @@ impl SvgRenderer { Self { asset_source } } - pub fn render(&self, params: &RenderSvgParams) -> Result>> { + pub(crate) fn render(&self, params: &RenderSvgParams) -> Result>> { if params.size.is_zero() { return Err(anyhow!("can't render at a zero size")); } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index ba974567a6..0f2be2497a 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -900,7 +900,13 @@ impl<'a> WindowContext<'a> { /// Indicate that this view has changed, which will invoke any observers and also mark the window as dirty. /// If this view or any of its ancestors are *cached*, notifying it will cause it or its ancestors to be redrawn. - pub fn notify(&mut self, view_id: EntityId) { + /// Note that this method will always cause a redraw, the entire window is refreshed if view_id is None. + pub fn notify(&mut self, view_id: Option) { + let Some(view_id) = view_id else { + self.refresh(); + return; + }; + for view_id in self .window .rendered_frame @@ -1165,13 +1171,7 @@ impl<'a> WindowContext<'a> { /// If called from within a view, it will notify that view on the next frame. Otherwise, it will refresh the entire window. pub fn request_animation_frame(&self) { let parent_id = self.parent_view_id(); - self.on_next_frame(move |cx| { - if let Some(parent_id) = parent_id { - cx.notify(parent_id) - } else { - cx.refresh() - } - }); + self.on_next_frame(move |cx| cx.notify(parent_id)); } /// Spawn the future returned by the given closure on the application thread pool. @@ -1982,9 +1982,7 @@ impl<'a> WindowContext<'a> { /// /// Note that the multiple calls to this method will only result in one `Asset::load` call at a /// time. - /// - /// This asset will not be cached by default, see [Self::use_cached_asset] - pub fn use_asset(&mut self, source: &A::Source) -> Option { + pub fn use_asset(&mut self, source: &A::Source) -> Option { let (task, is_first) = self.fetch_asset::(source); task.clone().now_or_never().or_else(|| { if is_first { @@ -1994,13 +1992,7 @@ impl<'a> WindowContext<'a> { |mut cx| async move { task.await; - cx.on_next_frame(move |cx| { - if let Some(parent_id) = parent_id { - cx.notify(parent_id) - } else { - cx.refresh() - } - }); + cx.on_next_frame(move |cx| cx.notify(parent_id)); } }) .detach(); @@ -2163,6 +2155,9 @@ impl<'a> WindowContext<'a> { /// A variant of `with_element_state` that allows the element's id to be optional. This is a convenience /// method for elements where the element id may or may not be assigned. Prefer using `with_element_state` /// when the element is guaranteed to have an id. + /// + /// The first option means 'no ID provided' + /// The second option means 'not yet initialized' pub fn with_optional_element_state( &mut self, global_id: Option<&GlobalElementId>, @@ -4227,7 +4222,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { /// Indicate that this view has changed, which will invoke any observers and also mark the window as dirty. /// If this view or any of its ancestors are *cached*, notifying it will cause it or its ancestors to be redrawn. pub fn notify(&mut self) { - self.window_cx.notify(self.view.entity_id()); + self.window_cx.notify(Some(self.view.entity_id())); } /// Register a callback to be invoked when the window is resized. diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index c8f09439c3..7dbda4b3fb 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/crates/ui/src/components/scrollbar.rs @@ -370,7 +370,7 @@ impl Element for Scrollbar { }; if let Some(id) = state.parent_id { - cx.notify(id); + cx.notify(Some(id)); } } } else { @@ -382,7 +382,7 @@ impl Element for Scrollbar { if phase.bubble() { state.drag.take(); if let Some(id) = state.parent_id { - cx.notify(id); + cx.notify(Some(id)); } } }); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 047e47e814..31089f7882 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -5896,7 +5896,7 @@ pub fn client_side_decorations(element: impl IntoElement, cx: &mut WindowContext let edge = cx.try_global::(); if new_edge != edge.map(|edge| edge.0) { cx.window_handle() - .update(cx, |workspace, cx| cx.notify(workspace.entity_id())) + .update(cx, |workspace, cx| cx.notify(Some(workspace.entity_id()))) .ok(); } }) From 21c785ede4430a5c20412390cd450666cf79a6e4 Mon Sep 17 00:00:00 2001 From: SweetPPro Date: Sat, 16 Nov 2024 12:20:52 +0100 Subject: [PATCH 009/157] Add more common Prettier plugin base paths (#20758) Closes #19024 Release Notes: - Added some more common Prettier plugin base paths --- crates/prettier/src/prettier.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 3a3b88cba1..92db62e6c6 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -225,6 +225,8 @@ impl Prettier { prettier_plugin_dir.join("dist").join("index.mjs"), prettier_plugin_dir.join("dist").join("index.js"), prettier_plugin_dir.join("dist").join("plugin.js"), + prettier_plugin_dir.join("src").join("plugin.js"), + prettier_plugin_dir.join("lib").join("index.js"), prettier_plugin_dir.join("index.mjs"), prettier_plugin_dir.join("index.js"), prettier_plugin_dir.join("plugin.js"), From b421ffafb57f9a62bf65167fd6a023f32716b720 Mon Sep 17 00:00:00 2001 From: Gowtham K <73059450+dovakin0007@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:54:30 +0530 Subject: [PATCH 010/157] Windows: Add transparency effect (#20400) Release Notes: - Added Transparency effect for Windows #19405 ![image](https://github.com/user-attachments/assets/b0750020-5a89-48c9-b26e-13b30874cf8d) ![image](https://github.com/user-attachments/assets/80609a14-b8c3-4159-b909-1e61f4c3eac3) --------- Co-authored-by: thedeveloper-sharath <35845141+thedeveloper-sharath@users.noreply.github.com> --- crates/gpui/src/platform/windows/window.rs | 97 +++++++++++++++++++++- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index 46b54e689d..f2600d3c6f 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -647,11 +647,47 @@ impl PlatformWindow for WindowsWindow { } fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) { - self.0 - .state - .borrow_mut() + let mut window_state = self.0.state.borrow_mut(); + window_state .renderer .update_transparency(background_appearance != WindowBackgroundAppearance::Opaque); + let mut version = unsafe { std::mem::zeroed() }; + let status = unsafe { windows::Wdk::System::SystemServices::RtlGetVersion(&mut version) }; + if status.is_ok() { + if background_appearance == WindowBackgroundAppearance::Blurred { + if version.dwBuildNumber >= 17763 { + set_window_composition_attribute(window_state.hwnd, Some((0, 0, 0, 10)), 4); + } + } else { + if version.dwBuildNumber >= 17763 { + set_window_composition_attribute(window_state.hwnd, None, 0); + } + } + //Transparent effect might cause some flickering and performance issues due `WS_EX_COMPOSITED` is enabled + //if `WS_EX_COMPOSITED` is removed the window instance won't initiate + if background_appearance == WindowBackgroundAppearance::Transparent { + unsafe { + let current_style = GetWindowLongW(window_state.hwnd, GWL_EXSTYLE); + SetWindowLongW( + window_state.hwnd, + GWL_EXSTYLE, + current_style | WS_EX_LAYERED.0 as i32 | WS_EX_COMPOSITED.0 as i32, + ); + SetLayeredWindowAttributes(window_state.hwnd, COLORREF(0), 225, LWA_ALPHA) + .inspect_err(|e| log::error!("Unable to set window to transparent: {e}")) + .ok(); + }; + } else { + unsafe { + let current_style = GetWindowLongW(window_state.hwnd, GWL_EXSTYLE); + SetWindowLongW( + window_state.hwnd, + GWL_EXSTYLE, + current_style & !WS_EX_LAYERED.0 as i32 & !WS_EX_COMPOSITED.0 as i32, + ); + } + } + } } fn minimize(&self) { @@ -932,6 +968,23 @@ struct StyleAndBounds { cy: i32, } +#[repr(C)] +struct WINDOWCOMPOSITIONATTRIBDATA { + attrib: u32, + pv_data: *mut std::ffi::c_void, + cb_data: usize, +} + +#[repr(C)] +struct AccentPolicy { + accent_state: u32, + accent_flags: u32, + gradient_color: u32, + animation_id: u32, +} + +type Color = (u8, u8, u8, u8); + #[derive(Debug, Default, Clone, Copy)] pub(crate) struct WindowBorderOffset { width_offset: i32, @@ -1136,6 +1189,44 @@ fn retrieve_window_placement( Ok(placement) } +fn set_window_composition_attribute(hwnd: HWND, color: Option, state: u32) { + unsafe { + type SetWindowCompositionAttributeType = + unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL; + let module_name = PCSTR::from_raw("user32.dll\0".as_ptr()); + let user32 = GetModuleHandleA(module_name); + if user32.is_ok() { + let func_name = PCSTR::from_raw("SetWindowCompositionAttribute\0".as_ptr()); + let set_window_composition_attribute: SetWindowCompositionAttributeType = + std::mem::transmute(GetProcAddress(user32.unwrap(), func_name)); + let mut color = color.unwrap_or_default(); + let is_acrylic = state == 4; + if is_acrylic && color.3 == 0 { + color.3 = 1; + } + let accent = AccentPolicy { + accent_state: state, + accent_flags: if is_acrylic { 0 } else { 2 }, + gradient_color: (color.0 as u32) + | ((color.1 as u32) << 8) + | ((color.2 as u32) << 16) + | (color.3 as u32) << 24, + animation_id: 0, + }; + let mut data = WINDOWCOMPOSITIONATTRIBDATA { + attrib: 0x13, + pv_data: &accent as *const _ as *mut _, + cb_data: std::mem::size_of::(), + }; + let _ = set_window_composition_attribute(hwnd, &mut data as *mut _ as _); + } else { + let _ = user32 + .inspect_err(|e| log::error!("Error getting module: {e}")) + .ok(); + } + } +} + mod windows_renderer { use std::{num::NonZeroIsize, sync::Arc}; From 792c1e47104a61e79b4505e66064e39f9f7ff57d Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Sat, 16 Nov 2024 19:36:13 +0800 Subject: [PATCH 011/157] gpui: Add paint_path example (#20499) Release Notes: - N/A --- ``` cargo run -p gpui --example painting ``` I added this demo to verify the detailed support of Path drawing in GPUI. Because of, when we actually used GPUI to draw a 2D line chart, we found that the straight line Path#line_to did not support `anti-aliasing`, and the drawn line looked very bad. As shown in the demo image, if we zoom in on the image, we can clearly see that all the lines are jagged. I read and tried to make some appropriate adjustments to the functions in Path, but since I have no experience in the graphics field, I still cannot achieve anti-aliasing support so far. I don't know if I used it wrong somewhere. I checked `curve_to` and found that the curves drawn have anti-aliasing effects, as shown in the arc part of the figure below. image --- crates/gpui/examples/painting.rs | 199 +++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 crates/gpui/examples/painting.rs diff --git a/crates/gpui/examples/painting.rs b/crates/gpui/examples/painting.rs new file mode 100644 index 0000000000..6e5fe25dfd --- /dev/null +++ b/crates/gpui/examples/painting.rs @@ -0,0 +1,199 @@ +use gpui::{ + canvas, div, point, prelude::*, px, size, App, AppContext, Bounds, MouseDownEvent, Path, + Pixels, Point, Render, ViewContext, WindowOptions, +}; +struct PaintingViewer { + default_lines: Vec>, + lines: Vec>>, + start: Point, + _painting: bool, +} + +impl PaintingViewer { + fn new() -> Self { + let mut lines = vec![]; + + // draw a line + let mut path = Path::new(point(px(50.), px(180.))); + path.line_to(point(px(100.), px(120.))); + // go back to close the path + path.line_to(point(px(100.), px(121.))); + path.line_to(point(px(50.), px(181.))); + lines.push(path); + + // draw a lightening bolt ⚡ + let mut path = Path::new(point(px(150.), px(200.))); + path.line_to(point(px(200.), px(125.))); + path.line_to(point(px(200.), px(175.))); + path.line_to(point(px(250.), px(100.))); + lines.push(path); + + // draw a ⭐ + let mut path = Path::new(point(px(350.), px(100.))); + path.line_to(point(px(370.), px(160.))); + path.line_to(point(px(430.), px(160.))); + path.line_to(point(px(380.), px(200.))); + path.line_to(point(px(400.), px(260.))); + path.line_to(point(px(350.), px(220.))); + path.line_to(point(px(300.), px(260.))); + path.line_to(point(px(320.), px(200.))); + path.line_to(point(px(270.), px(160.))); + path.line_to(point(px(330.), px(160.))); + path.line_to(point(px(350.), px(100.))); + lines.push(path); + + let square_bounds = Bounds { + origin: point(px(450.), px(100.)), + size: size(px(200.), px(80.)), + }; + let height = square_bounds.size.height; + let horizontal_offset = height; + let vertical_offset = px(30.); + let mut path = Path::new(square_bounds.lower_left()); + path.curve_to( + square_bounds.origin + point(horizontal_offset, vertical_offset), + square_bounds.origin + point(px(0.0), vertical_offset), + ); + path.line_to(square_bounds.upper_right() + point(-horizontal_offset, vertical_offset)); + path.curve_to( + square_bounds.lower_right(), + square_bounds.upper_right() + point(px(0.0), vertical_offset), + ); + path.line_to(square_bounds.lower_left()); + lines.push(path); + + Self { + default_lines: lines.clone(), + lines: vec![], + start: point(px(0.), px(0.)), + _painting: false, + } + } + + fn clear(&mut self, cx: &mut ViewContext) { + self.lines.clear(); + cx.notify(); + } +} +impl Render for PaintingViewer { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let default_lines = self.default_lines.clone(); + let lines = self.lines.clone(); + div() + .font_family(".SystemUIFont") + .bg(gpui::white()) + .size_full() + .p_4() + .flex() + .flex_col() + .child( + div() + .flex() + .gap_2() + .justify_between() + .items_center() + .child("Mouse down any point and drag to draw lines (Hold on shift key to draw straight lines)") + .child( + div() + .id("clear") + .child("Clean up") + .bg(gpui::black()) + .text_color(gpui::white()) + .active(|this| this.opacity(0.8)) + .flex() + .px_3() + .py_1() + .on_click(cx.listener(|this, _, cx| { + this.clear(cx); + })), + ), + ) + .child( + div() + .size_full() + .child( + canvas( + move |_, _| {}, + move |_, _, cx| { + const STROKE_WIDTH: Pixels = px(2.0); + for path in default_lines { + cx.paint_path(path, gpui::black()); + } + for points in lines { + let mut path = Path::new(points[0]); + for p in points.iter().skip(1) { + path.line_to(*p); + } + + let mut last = points.last().unwrap(); + for p in points.iter().rev() { + let mut offset_x = px(0.); + if last.x == p.x { + offset_x = STROKE_WIDTH; + } + path.line_to(point(p.x + offset_x, p.y + STROKE_WIDTH)); + last = p; + } + + cx.paint_path(path, gpui::black()); + } + }, + ) + .size_full(), + ) + .on_mouse_down( + gpui::MouseButton::Left, + cx.listener(|this, ev: &MouseDownEvent, _| { + this._painting = true; + this.start = ev.position; + let path = vec![ev.position]; + this.lines.push(path); + }), + ) + .on_mouse_move(cx.listener(|this, ev: &gpui::MouseMoveEvent, cx| { + if !this._painting { + return; + } + + let is_shifted = ev.modifiers.shift; + let mut pos = ev.position; + // When holding shift, draw a straight line + if is_shifted { + let dx = pos.x - this.start.x; + let dy = pos.y - this.start.y; + if dx.abs() > dy.abs() { + pos.y = this.start.y; + } else { + pos.x = this.start.x; + } + } + + if let Some(path) = this.lines.last_mut() { + path.push(pos); + } + + cx.notify(); + })) + .on_mouse_up( + gpui::MouseButton::Left, + cx.listener(|this, _, _| { + this._painting = false; + }), + ), + ) + } +} + +fn main() { + App::new().run(|cx: &mut AppContext| { + cx.open_window( + WindowOptions { + focus: true, + ..Default::default() + }, + |cx| cx.new_view(|_| PaintingViewer::new()), + ) + .unwrap(); + cx.activate(true); + }); +} From 33f09bad60cfeb9c1ad8c93a62cce08e037ac8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8B=90=E7=8B=B8?= <134658521+Huliiiiii@users.noreply.github.com> Date: Sat, 16 Nov 2024 19:42:10 +0800 Subject: [PATCH 012/157] Highlight `?` and `:` in ternary expressions as operator in JavaScript, TypeScript, and TSX (#20573) Closes #ISSUE Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/languages/src/javascript/highlights.scm | 7 +++++++ crates/languages/src/tsx/highlights.scm | 7 +++++++ crates/languages/src/typescript/highlights.scm | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/crates/languages/src/javascript/highlights.scm b/crates/languages/src/javascript/highlights.scm index a89375dee2..e5d4cb2068 100644 --- a/crates/languages/src/javascript/highlights.scm +++ b/crates/languages/src/javascript/highlights.scm @@ -150,6 +150,13 @@ "}" ] @punctuation.bracket +(ternary_expression + [ + "?" + ":" + ] @operator +) + [ "as" "async" diff --git a/crates/languages/src/tsx/highlights.scm b/crates/languages/src/tsx/highlights.scm index bbdd83bb4d..26cf5c207b 100644 --- a/crates/languages/src/tsx/highlights.scm +++ b/crates/languages/src/tsx/highlights.scm @@ -155,6 +155,13 @@ "}" ] @punctuation.bracket +(ternary_expression + [ + "?" + ":" + ] @operator +) + [ "as" "async" diff --git a/crates/languages/src/typescript/highlights.scm b/crates/languages/src/typescript/highlights.scm index eedcf79aed..f7b893da7a 100644 --- a/crates/languages/src/typescript/highlights.scm +++ b/crates/languages/src/typescript/highlights.scm @@ -156,6 +156,13 @@ "}" ] @punctuation.bracket +(ternary_expression + [ + "?" + ":" + ] @operator +) + [ "as" "async" From 65a9c8d994673296c2b42ab55d97f4b29063f173 Mon Sep 17 00:00:00 2001 From: Aaron Ruan Date: Sat, 16 Nov 2024 19:48:25 +0800 Subject: [PATCH 013/157] Dynamic tab bar height (#19076) Tracking issue: https://github.com/zed-industries/zed/issues/18078 Release Notes: - Change tab bar height according to `ui-density` --------- Signed-off-by: Aaron Ruan --- crates/collab_ui/src/chat_panel.rs | 4 ++-- crates/collab_ui/src/notification_panel.rs | 6 ++++-- crates/ui/src/components/tab.rs | 18 +++++++++++------- crates/ui/src/components/tab_bar.rs | 7 ++----- crates/workspace/src/pane.rs | 2 -- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 5a79f364ff..b330b5b531 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -23,7 +23,7 @@ use std::{sync::Arc, time::Duration}; use time::{OffsetDateTime, UtcOffset}; use ui::{ prelude::*, Avatar, Button, ContextMenu, IconButton, IconName, KeyBinding, Label, PopoverMenu, - TabBar, Tooltip, + Tab, TabBar, Tooltip, }; use util::{ResultExt, TryFutureExt}; use workspace::{ @@ -939,7 +939,7 @@ impl Render for ChatPanel { TabBar::new("chat_header").child( h_flex() .w_full() - .h(rems(ui::Tab::CONTAINER_HEIGHT_IN_REMS)) + .h(Tab::container_height(cx)) .px_2() .child(Label::new( self.active_chat diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 33ca5a2952..410b90c727 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -19,7 +19,9 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::{sync::Arc, time::Duration}; use time::{OffsetDateTime, UtcOffset}; -use ui::{h_flex, prelude::*, v_flex, Avatar, Button, Icon, IconButton, IconName, Label, Tooltip}; +use ui::{ + h_flex, prelude::*, v_flex, Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip, +}; use util::{ResultExt, TryFutureExt}; use workspace::notifications::NotificationId; use workspace::{ @@ -588,7 +590,7 @@ impl Render for NotificationPanel { .px_2() .py_1() // Match the height of the tab bar so they line up. - .h(rems(ui::Tab::CONTAINER_HEIGHT_IN_REMS)) + .h(Tab::container_height(cx)) .border_b_1() .border_color(cx.theme().colors().border) .child(Label::new("Notifications")) diff --git a/crates/ui/src/components/tab.rs b/crates/ui/src/components/tab.rs index 1a2c1595cf..e33fc732da 100644 --- a/crates/ui/src/components/tab.rs +++ b/crates/ui/src/components/tab.rs @@ -4,7 +4,7 @@ use std::cmp::Ordering; use gpui::{AnyElement, IntoElement, Stateful}; use smallvec::SmallVec; -use crate::{prelude::*, BASE_REM_SIZE_IN_PX}; +use crate::prelude::*; /// The position of a [`Tab`] within a list of tabs. #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -54,10 +54,6 @@ impl Tab { } } - pub const CONTAINER_HEIGHT_IN_REMS: f32 = 29. / BASE_REM_SIZE_IN_PX; - - const CONTENT_HEIGHT_IN_REMS: f32 = 28. / BASE_REM_SIZE_IN_PX; - pub fn position(mut self, position: TabPosition) -> Self { self.position = position; self @@ -77,6 +73,14 @@ impl Tab { self.end_slot = element.into().map(IntoElement::into_any_element); self } + + pub fn content_height(cx: &mut WindowContext) -> Pixels { + DynamicSpacing::Base32.px(cx) - px(1.) + } + + pub fn container_height(cx: &mut WindowContext) -> Pixels { + DynamicSpacing::Base32.px(cx) + } } impl InteractiveElement for Tab { @@ -130,7 +134,7 @@ impl RenderOnce for Tab { }; self.div - .h(rems(Self::CONTAINER_HEIGHT_IN_REMS)) + .h(Tab::container_height(cx)) .bg(tab_bg) .border_color(cx.theme().colors().border) .map(|this| match self.position { @@ -157,7 +161,7 @@ impl RenderOnce for Tab { h_flex() .group("") .relative() - .h(rems(Self::CONTENT_HEIGHT_IN_REMS)) + .h(Tab::content_height(cx)) .px(DynamicSpacing::Base04.px(cx)) .gap(DynamicSpacing::Base04.rems(cx)) .text_color(text_color) diff --git a/crates/ui/src/components/tab_bar.rs b/crates/ui/src/components/tab_bar.rs index 6aaa2fd59a..7a169f9701 100644 --- a/crates/ui/src/components/tab_bar.rs +++ b/crates/ui/src/components/tab_bar.rs @@ -3,6 +3,7 @@ use gpui::{AnyElement, ScrollHandle}; use smallvec::SmallVec; use crate::prelude::*; +use crate::Tab; #[derive(IntoElement)] pub struct TabBar { @@ -97,11 +98,7 @@ impl RenderOnce for TabBar { .flex() .flex_none() .w_full() - .h( - // TODO: This should scale with [UiDensity], however tabs, - // and other tab bar tools need to scale dynamically first. - rems_from_px(29.), - ) + .h(Tab::container_height(cx)) .bg(cx.theme().colors().tab_bar_background) .when(!self.start_children.is_empty(), |this| { this.child( diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 52018f53c8..22d06ec21a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2259,7 +2259,6 @@ impl Pane { fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl IntoElement { let focus_handle = self.focus_handle.clone(); let navigate_backward = IconButton::new("navigate_backward", IconName::ArrowLeft) - .shape(IconButtonShape::Square) .icon_size(IconSize::Small) .on_click({ let view = cx.view().clone(); @@ -2272,7 +2271,6 @@ impl Pane { }); let navigate_forward = IconButton::new("navigate_forward", IconName::ArrowRight) - .shape(IconButtonShape::Square) .icon_size(IconSize::Small) .on_click({ let view = cx.view().clone(); From 932c7e23c8863fce83cb85cc5677ee421fb192b1 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Sat, 16 Nov 2024 22:53:57 +0800 Subject: [PATCH 014/157] gpui: Fix SVG color render, when color have alpha (#20537) Release Notes: - N/A ## Demo - [Source SVG](https://github.com/user-attachments/assets/1c681e01-baba-4613-a3e7-ea5cb3015406) click here open in browser. | Before | After | | --- | --- | | image | image | --------- Co-authored-by: Floyd Wang --- crates/gpui/examples/image/color.svg | 6 +++--- crates/gpui/src/color.rs | 11 +++++++++++ crates/gpui/src/elements/img.rs | 11 +++++------ crates/gpui/src/platform/mac/text_system.rs | 13 +++++-------- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/crates/gpui/examples/image/color.svg b/crates/gpui/examples/image/color.svg index 84e9809d09..a080681b0e 100644 --- a/crates/gpui/examples/image/color.svg +++ b/crates/gpui/examples/image/color.svg @@ -6,8 +6,8 @@ - + - - \ No newline at end of file + + diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index 6a1f375b65..9c831d0875 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -22,6 +22,17 @@ pub fn rgba(hex: u32) -> Rgba { Rgba { r, g, b, a } } +/// Swap from RGBA with premultiplied alpha to BGRA +pub(crate) fn swap_rgba_pa_to_bgra(color: &mut [u8]) { + color.swap(0, 2); + if color[3] > 0 { + let a = color[3] as f32 / 255.; + color[0] = (color[0] as f32 / a) as u8; + color[1] = (color[1] as f32 / a) as u8; + color[2] = (color[2] as f32 / a) as u8; + } +} + /// An RGBA color #[derive(PartialEq, Clone, Copy, Default)] pub struct Rgba { diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 4e32672095..895904c801 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -1,8 +1,8 @@ use crate::{ - px, AbsoluteLength, AnyElement, AppContext, Asset, AssetLogger, Bounds, DefiniteLength, - Element, ElementId, GlobalElementId, Hitbox, Image, InteractiveElement, Interactivity, - IntoElement, LayoutId, Length, ObjectFit, Pixels, RenderImage, Resource, SharedString, - SharedUri, StyleRefinement, Styled, SvgSize, Task, WindowContext, + px, swap_rgba_pa_to_bgra, AbsoluteLength, AnyElement, AppContext, Asset, AssetLogger, Bounds, + DefiniteLength, Element, ElementId, GlobalElementId, Hitbox, Image, InteractiveElement, + Interactivity, IntoElement, LayoutId, Length, ObjectFit, Pixels, RenderImage, Resource, + SharedString, SharedUri, StyleRefinement, Styled, SvgSize, Task, WindowContext, }; use anyhow::{anyhow, Result}; @@ -564,9 +564,8 @@ impl Asset for ImageAssetLoader { let mut buffer = ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take()).unwrap(); - // Convert from RGBA to BGRA. for pixel in buffer.chunks_exact_mut(4) { - pixel.swap(0, 2); + swap_rgba_pa_to_bgra(pixel); } RenderImage::new(SmallVec::from_elem(Frame::new(buffer), 1)) diff --git a/crates/gpui/src/platform/mac/text_system.rs b/crates/gpui/src/platform/mac/text_system.rs index 3db1bf9bcd..560c78ffb2 100644 --- a/crates/gpui/src/platform/mac/text_system.rs +++ b/crates/gpui/src/platform/mac/text_system.rs @@ -1,7 +1,8 @@ use crate::{ - point, px, size, Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, FontId, FontMetrics, - FontRun, FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, - RenderGlyphParams, Result, ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS, + point, px, size, swap_rgba_pa_to_bgra, Bounds, DevicePixels, Font, FontFallbacks, FontFeatures, + FontId, FontMetrics, FontRun, FontStyle, FontWeight, GlyphId, LineLayout, Pixels, + PlatformTextSystem, Point, RenderGlyphParams, Result, ShapedGlyph, ShapedRun, SharedString, + Size, SUBPIXEL_VARIANTS, }; use anyhow::anyhow; use cocoa::appkit::CGFloat; @@ -418,11 +419,7 @@ impl MacTextSystemState { if params.is_emoji { // Convert from RGBA with premultiplied alpha to BGRA with straight alpha. for pixel in bytes.chunks_exact_mut(4) { - pixel.swap(0, 2); - let a = pixel[3] as f32 / 255.; - pixel[0] = (pixel[0] as f32 / a) as u8; - pixel[1] = (pixel[1] as f32 / a) as u8; - pixel[2] = (pixel[2] as f32 / a) as u8; + swap_rgba_pa_to_bgra(pixel); } } From 97e9137cb7b46a56dd59504b91de310e8155af91 Mon Sep 17 00:00:00 2001 From: "Siddharth M. Bhatia" Date: Sat, 16 Nov 2024 08:18:53 -0800 Subject: [PATCH 015/157] Update references of Ollama Llama 3.1 to model Llama 3.2 (#20757) Release Notes: - N/A --- crates/language_model/src/provider/ollama.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/language_model/src/provider/ollama.rs b/crates/language_model/src/provider/ollama.rs index ac79bb2ed5..3485982781 100644 --- a/crates/language_model/src/provider/ollama.rs +++ b/crates/language_model/src/provider/ollama.rs @@ -35,7 +35,7 @@ pub struct OllamaSettings { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct AvailableModel { - /// The model name in the Ollama API (e.g. "llama3.1:latest") + /// The model name in the Ollama API (e.g. "llama3.2:latest") pub name: String, /// The model's name in Zed's UI, such as in the model selector dropdown menu in the assistant panel. pub display_name: Option, @@ -446,7 +446,7 @@ impl Render for ConfigurationView { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let is_authenticated = self.state.read(cx).is_authenticated(); - let ollama_intro = "Get up and running with Llama 3.1, Mistral, Gemma 2, and other large language models with Ollama."; + let ollama_intro = "Get up and running with Llama 3.2, Mistral, Gemma 2, and other large language models with Ollama."; let ollama_reqs = "Ollama must be running with at least one model installed to use it in the assistant."; @@ -475,7 +475,7 @@ impl Render for ConfigurationView { .bg(inline_code_bg) .px_1p5() .rounded_md() - .child(Label::new("ollama run llama3.1")), + .child(Label::new("ollama run llama3.2")), ), ), ) From f9990b42fa3ef81b53d0b0f42c88836e319758f1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 16 Nov 2024 09:58:19 -0700 Subject: [PATCH 016/157] Send events to Snowflake in the format they're expected by Amplitude (#20765) This will allow us to use the events table directly in Amplitude, which lets us use the newer event ingestion flow that detects changes to the table. Otherwise we'll need a transformation. I think Amplitude's API is probably a pretty good example to follow for the raw event schema, even if we don't end up using their product. They also recommend a "Noun Verbed" format for naming events, so I think we should go with this. This will help us be consistent and encourage the author of events to think more clearly about what event they're reporting. cc @ConradIrwin Release Notes: - N/A --- crates/collab/src/api/events.rs | 278 +++++++++++++++++++++----------- 1 file changed, 182 insertions(+), 96 deletions(-) diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 9d487c8038..3b7a3b3900 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -15,6 +15,7 @@ use chrono::Duration; use rpc::ExtensionMetadata; use semantic_version::SemanticVersion; use serde::{Deserialize, Serialize, Serializer}; +use serde_json::json; use sha2::{Digest, Sha256}; use std::sync::{Arc, OnceLock}; use telemetry_events::{ @@ -1392,111 +1393,196 @@ fn for_snowflake( body: EventRequestBody, first_event_at: chrono::DateTime, ) -> impl Iterator { - body.events.into_iter().map(move |event| SnowflakeRow { - event: match &event.event { - Event::Editor(editor_event) => format!("editor_{}", editor_event.operation), - Event::InlineCompletion(inline_completion_event) => format!( - "inline_completion_{}", - if inline_completion_event.suggestion_accepted { - "accept " - } else { - "discard" - } + body.events.into_iter().map(move |event| { + let timestamp = + first_event_at + Duration::milliseconds(event.milliseconds_since_first_event); + let (event_type, mut event_properties) = match &event.event { + Event::Editor(e) => ( + match e.operation.as_str() { + "open" => "Editor Opened".to_string(), + "save" => "Editor Saved".to_string(), + _ => format!("Unknown Editor Event: {}", e.operation), + }, + serde_json::to_value(e).unwrap(), ), - Event::Call(call_event) => format!("call_{}", call_event.operation.replace(" ", "_")), - Event::Assistant(assistant_event) => { + Event::InlineCompletion(e) => ( format!( - "assistant_{}", - match assistant_event.phase { - telemetry_events::AssistantPhase::Response => "response", - telemetry_events::AssistantPhase::Invoked => "invoke", - telemetry_events::AssistantPhase::Accepted => "accept", - telemetry_events::AssistantPhase::Rejected => "reject", + "Inline Completion {}", + if e.suggestion_accepted { + "Accepted" + } else { + "Discarded" } - ) + ), + serde_json::to_value(e).unwrap(), + ), + Event::Call(e) => { + let event_type = match e.operation.trim() { + "unshare project" => "Project Unshared".to_string(), + "open channel notes" => "Channel Notes Opened".to_string(), + "share project" => "Project Shared".to_string(), + "join channel" => "Channel Joined".to_string(), + "hang up" => "Call Ended".to_string(), + "accept incoming" => "Incoming Call Accepted".to_string(), + "invite" => "Participant Invited".to_string(), + "disable microphone" => "Microphone Disabled".to_string(), + "enable microphone" => "Microphone Enabled".to_string(), + "enable screen share" => "Screen Share Enabled".to_string(), + "disable screen share" => "Screen Share Disabled".to_string(), + "decline incoming" => "Incoming Call Declined".to_string(), + "enable camera" => "Camera Enabled".to_string(), + "disable camera" => "Camera Disabled".to_string(), + _ => format!("Unknown Call Event: {}", e.operation), + }; + + (event_type, serde_json::to_value(e).unwrap()) } - Event::Cpu(_) => "system_cpu".to_string(), - Event::Memory(_) => "system_memory".to_string(), - Event::App(app_event) => app_event.operation.replace(" ", "_"), - Event::Setting(_) => "setting_change".to_string(), - Event::Extension(_) => "extension_load".to_string(), - Event::Edit(_) => "edit".to_string(), - Event::Action(_) => "command_palette_action".to_string(), - Event::Repl(_) => "repl".to_string(), - }, - system_id: body.system_id.clone(), - timestamp: first_event_at + Duration::milliseconds(event.milliseconds_since_first_event), - data: SnowflakeData { - installation_id: body.installation_id.clone(), - session_id: body.session_id.clone(), - metrics_id: body.metrics_id.clone(), - is_staff: body.is_staff, - app_version: body.app_version.clone(), - os_name: body.os_name.clone(), - os_version: body.os_version.clone(), - architecture: body.architecture.clone(), - release_channel: body.release_channel.clone(), - signed_in: event.signed_in, - editor_event: match &event.event { - Event::Editor(editor_event) => Some(editor_event.clone()), - _ => None, - }, - inline_completion_event: match &event.event { - Event::InlineCompletion(inline_completion_event) => { - Some(inline_completion_event.clone()) - } - _ => None, - }, - call_event: match &event.event { - Event::Call(call_event) => Some(call_event.clone()), - _ => None, - }, - assistant_event: match &event.event { - Event::Assistant(assistant_event) => Some(assistant_event.clone()), - _ => None, - }, - cpu_event: match &event.event { - Event::Cpu(cpu_event) => Some(cpu_event.clone()), - _ => None, - }, - memory_event: match &event.event { - Event::Memory(memory_event) => Some(memory_event.clone()), - _ => None, - }, - app_event: match &event.event { - Event::App(app_event) => Some(app_event.clone()), - _ => None, - }, - setting_event: match &event.event { - Event::Setting(setting_event) => Some(setting_event.clone()), - _ => None, - }, - extension_event: match &event.event { - Event::Extension(extension_event) => Some(extension_event.clone()), - _ => None, - }, - edit_event: match &event.event { - Event::Edit(edit_event) => Some(edit_event.clone()), - _ => None, - }, - repl_event: match &event.event { - Event::Repl(repl_event) => Some(repl_event.clone()), - _ => None, - }, - action_event: match event.event { - Event::Action(action_event) => Some(action_event.clone()), - _ => None, - }, - }, + Event::Assistant(e) => ( + match e.phase { + telemetry_events::AssistantPhase::Response => "Assistant Responded".to_string(), + telemetry_events::AssistantPhase::Invoked => "Assistant Invoked".to_string(), + telemetry_events::AssistantPhase::Accepted => { + "Assistant Response Accepted".to_string() + } + telemetry_events::AssistantPhase::Rejected => { + "Assistant Response Rejected".to_string() + } + }, + serde_json::to_value(e).unwrap(), + ), + Event::Cpu(e) => ( + "System CPU Sampled".to_string(), + serde_json::to_value(e).unwrap(), + ), + Event::Memory(e) => ( + "System Memory Sampled".to_string(), + serde_json::to_value(e).unwrap(), + ), + Event::App(e) => { + let mut properties = json!({}); + let event_type = match e.operation.trim() { + "extensions: install extension" => "Extension Installed".to_string(), + "open" => "App Opened".to_string(), + "project search: open" => "Project Search Opened".to_string(), + "first open" => { + properties["is_first_open"] = json!(true); + "App First Opened".to_string() + } + "extensions: uninstall extension" => "Extension Uninstalled".to_string(), + "welcome page: close" => "Welcome Page Closed".to_string(), + "open project" => { + properties["is_first_time"] = json!(false); + "Project Opened".to_string() + } + "welcome page: install cli" => "CLI Installed".to_string(), + "project diagnostics: open" => "Project Diagnostics Opened".to_string(), + "extensions page: open" => "Extensions Page Opened".to_string(), + "welcome page: change theme" => "Welcome Theme Changed".to_string(), + "welcome page: toggle metric telemetry" => { + properties["enabled"] = json!(false); + "Welcome Telemetry Toggled".to_string() + } + "welcome page: change keymap" => "Keymap Changed".to_string(), + "welcome page: toggle vim" => { + properties["enabled"] = json!(false); + "Welcome Vim Mode Toggled".to_string() + } + "welcome page: sign in to copilot" => "Welcome Copilot Signed In".to_string(), + "welcome page: toggle diagnostic telemetry" => { + "Welcome Telemetry Toggled".to_string() + } + "welcome page: open" => "Welcome Page Opened".to_string(), + "close" => "App Closed".to_string(), + "markdown preview: open" => "Markdown Preview Opened".to_string(), + "welcome page: open extensions" => "Extensions Page Opened".to_string(), + "open node project" | "open pnpm project" | "open yarn project" => { + properties["project_type"] = json!("node"); + properties["is_first_time"] = json!(false); + "Project Opened".to_string() + } + "repl sessions: open" => "REPL Session Started".to_string(), + "welcome page: toggle helix" => { + properties["enabled"] = json!(false); + "Helix Mode Toggled".to_string() + } + "welcome page: edit settings" => { + properties["changed_settings"] = json!([]); + "Settings Edited".to_string() + } + "welcome page: view docs" => "Documentation Viewed".to_string(), + "open ssh project" => { + properties["is_first_time"] = json!(false); + "SSH Project Opened".to_string() + } + "create ssh server" => "SSH Server Created".to_string(), + "create ssh project" => "SSH Project Created".to_string(), + "first open for release channel" => { + properties["is_first_for_channel"] = json!(true); + "App First Opened For Release Channel".to_string() + } + _ => format!("Unknown App Event: {}", e.operation), + }; + (event_type, properties) + } + Event::Setting(e) => ( + "Settings Changed".to_string(), + serde_json::to_value(e).unwrap(), + ), + Event::Extension(e) => ( + "Extension Loaded".to_string(), + serde_json::to_value(e).unwrap(), + ), + Event::Edit(e) => ( + "Editor Edited".to_string(), + serde_json::to_value(e).unwrap(), + ), + Event::Action(e) => ( + "Action Invoked".to_string(), + serde_json::to_value(e).unwrap(), + ), + Event::Repl(e) => ( + "Kernel Status Changed".to_string(), + serde_json::to_value(e).unwrap(), + ), + }; + + if let serde_json::Value::Object(ref mut map) = event_properties { + map.insert("app_version".to_string(), body.app_version.clone().into()); + map.insert("os_name".to_string(), body.os_name.clone().into()); + map.insert("os_version".to_string(), body.os_version.clone().into()); + map.insert("architecture".to_string(), body.architecture.clone().into()); + map.insert( + "release_channel".to_string(), + body.release_channel.clone().into(), + ); + map.insert("signed_in".to_string(), event.signed_in.into()); + } + + let user_properties = Some(serde_json::json!({ + "is_staff": body.is_staff, + })); + + SnowflakeRow { + time: timestamp, + user_id: body.metrics_id.clone(), + device_id: body.system_id.clone(), + event_type, + event_properties, + user_properties, + insert_id: Some(Uuid::new_v4().to_string()), + } }) } #[derive(Serialize, Deserialize)] struct SnowflakeRow { - pub event: String, - pub system_id: Option, - pub timestamp: chrono::DateTime, - pub data: SnowflakeData, + pub time: chrono::DateTime, + pub user_id: Option, + pub device_id: Option, + pub event_type: String, + pub event_properties: serde_json::Value, + pub user_properties: Option, + pub insert_id: Option, } #[derive(Serialize, Deserialize)] From 2d3476530e9135ab31acac510f0411608ae4d397 Mon Sep 17 00:00:00 2001 From: Lu Wan Date: Sat, 16 Nov 2024 10:23:49 -0800 Subject: [PATCH 017/157] lsp: Retrieve links to documentation for the given symbol (#19233) Closes #18924 Release Notes: - Added an `editor:OpenDocs` action to open links to documentation via rust-analyzer --- crates/editor/src/actions.rs | 1 + crates/editor/src/rust_analyzer_ext.rs | 66 ++++++++++++- crates/lsp/src/lsp.rs | 1 + crates/project/src/lsp_ext_command.rs | 126 +++++++++++++++++++++++++ crates/proto/proto/zed.proto | 17 +++- crates/proto/src/proto.rs | 4 + 6 files changed, 213 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 3d932b931a..dcfc291968 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -297,6 +297,7 @@ gpui::actions!( OpenExcerptsSplit, OpenProposedChangesEditor, OpenFile, + OpenDocs, OpenPermalinkToLine, OpenUrl, Outdent, diff --git a/crates/editor/src/rust_analyzer_ext.rs b/crates/editor/src/rust_analyzer_ext.rs index fa39e5c9d4..ba14f91ed2 100644 --- a/crates/editor/src/rust_analyzer_ext.rs +++ b/crates/editor/src/rust_analyzer_ext.rs @@ -1,3 +1,5 @@ +use std::{fs, path::Path}; + use anyhow::Context as _; use gpui::{Context, View, ViewContext, VisualContext, WindowContext}; use language::Language; @@ -7,7 +9,7 @@ use text::ToPointUtf16; use crate::{ element::register_action, lsp_ext::find_specific_language_server_in_selection, Editor, - ExpandMacroRecursively, + ExpandMacroRecursively, OpenDocs, }; const RUST_ANALYZER_NAME: &str = "rust-analyzer"; @@ -24,6 +26,7 @@ pub fn apply_related_actions(editor: &View, cx: &mut WindowContext) { .is_some() { register_action(editor, cx, expand_macro_recursively); + register_action(editor, cx, open_docs); } } @@ -94,3 +97,64 @@ pub fn expand_macro_recursively( }) .detach_and_log_err(cx); } + +pub fn open_docs(editor: &mut Editor, _: &OpenDocs, cx: &mut ViewContext<'_, Editor>) { + if editor.selections.count() == 0 { + return; + } + let Some(project) = &editor.project else { + return; + }; + let Some(workspace) = editor.workspace() else { + return; + }; + + let Some((trigger_anchor, _rust_language, server_to_query, buffer)) = + find_specific_language_server_in_selection( + editor, + cx, + is_rust_language, + RUST_ANALYZER_NAME, + ) + else { + return; + }; + + let project = project.clone(); + let buffer_snapshot = buffer.read(cx).snapshot(); + let position = trigger_anchor.text_anchor.to_point_utf16(&buffer_snapshot); + let open_docs_task = project.update(cx, |project, cx| { + project.request_lsp( + buffer, + project::LanguageServerToQuery::Other(server_to_query), + project::lsp_ext_command::OpenDocs { position }, + cx, + ) + }); + + cx.spawn(|_editor, mut cx| async move { + let docs_urls = open_docs_task.await.context("open docs")?; + if docs_urls.is_empty() { + log::debug!("Empty docs urls for position {position:?}"); + return Ok(()); + } else { + log::debug!("{:?}", docs_urls); + } + + workspace.update(&mut cx, |_workspace, cx| { + // Check if the local document exists, otherwise fallback to the online document. + // Open with the default browser. + if let Some(local_url) = docs_urls.local { + if fs::metadata(Path::new(&local_url[8..])).is_ok() { + cx.open_url(&local_url); + return; + } + } + + if let Some(web_url) = docs_urls.web { + cx.open_url(&web_url); + } + }) + }) + .detach_and_log_err(cx); +} diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index ca09ef4d1f..5f0186e61e 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -762,6 +762,7 @@ impl LanguageServer { }), experimental: Some(json!({ "serverStatusNotification": true, + "localDocs": true, })), window: Some(WindowClientCapabilities { work_done_progress: Some(true), diff --git a/crates/project/src/lsp_ext_command.rs b/crates/project/src/lsp_ext_command.rs index 9fa1dc5480..7890630e31 100644 --- a/crates/project/src/lsp_ext_command.rs +++ b/crates/project/src/lsp_ext_command.rs @@ -134,6 +134,132 @@ impl LspCommand for ExpandMacro { } } +pub enum LspOpenDocs {} + +impl lsp::request::Request for LspOpenDocs { + type Params = OpenDocsParams; + type Result = Option; + const METHOD: &'static str = "experimental/externalDocs"; +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct OpenDocsParams { + pub text_document: lsp::TextDocumentIdentifier, + pub position: lsp::Position, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct DocsUrls { + pub web: Option, + pub local: Option, +} + +impl DocsUrls { + pub fn is_empty(&self) -> bool { + self.web.is_none() && self.local.is_none() + } +} + +#[derive(Debug)] +pub struct OpenDocs { + pub position: PointUtf16, +} + +#[async_trait(?Send)] +impl LspCommand for OpenDocs { + type Response = DocsUrls; + type LspRequest = LspOpenDocs; + type ProtoRequest = proto::LspExtOpenDocs; + + fn to_lsp( + &self, + path: &Path, + _: &Buffer, + _: &Arc, + _: &AppContext, + ) -> OpenDocsParams { + OpenDocsParams { + text_document: lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(path).unwrap(), + }, + position: point_to_lsp(self.position), + } + } + + async fn response_from_lsp( + self, + message: Option, + _: Model, + _: Model, + _: LanguageServerId, + _: AsyncAppContext, + ) -> anyhow::Result { + Ok(message + .map(|message| DocsUrls { + web: message.web, + local: message.local, + }) + .unwrap_or_default()) + } + + fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::LspExtOpenDocs { + proto::LspExtOpenDocs { + project_id, + buffer_id: buffer.remote_id().into(), + position: Some(language::proto::serialize_anchor( + &buffer.anchor_before(self.position), + )), + } + } + + async fn from_proto( + message: Self::ProtoRequest, + _: Model, + buffer: Model, + mut cx: AsyncAppContext, + ) -> anyhow::Result { + let position = message + .position + .and_then(deserialize_anchor) + .context("invalid position")?; + Ok(Self { + position: buffer.update(&mut cx, |buffer, _| position.to_point_utf16(buffer))?, + }) + } + + fn response_to_proto( + response: DocsUrls, + _: &mut LspStore, + _: PeerId, + _: &clock::Global, + _: &mut AppContext, + ) -> proto::LspExtOpenDocsResponse { + proto::LspExtOpenDocsResponse { + web: response.web, + local: response.local, + } + } + + async fn response_from_proto( + self, + message: proto::LspExtOpenDocsResponse, + _: Model, + _: Model, + _: AsyncAppContext, + ) -> anyhow::Result { + Ok(DocsUrls { + web: message.web, + local: message.local, + }) + } + + fn buffer_id_from_proto(message: &proto::LspExtOpenDocs) -> Result { + BufferId::new(message.buffer_id) + } +} + pub enum LspSwitchSourceHeader {} impl lsp::request::Request for LspSwitchSourceHeader { diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index b86894bae1..dcd62751a7 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -276,6 +276,7 @@ message Envelope { LanguageServerPromptRequest language_server_prompt_request = 268; LanguageServerPromptResponse language_server_prompt_response = 269; + GitBranches git_branches = 270; GitBranchesResponse git_branches_response = 271; @@ -293,7 +294,10 @@ message Envelope { GetPanicFiles get_panic_files = 280; GetPanicFilesResponse get_panic_files_response = 281; - CancelLanguageServerWork cancel_language_server_work = 282; // current max + CancelLanguageServerWork cancel_language_server_work = 282; + + LspExtOpenDocs lsp_ext_open_docs = 283; + LspExtOpenDocsResponse lsp_ext_open_docs_response = 284; // current max } reserved 87 to 88; @@ -2024,6 +2028,17 @@ message LspExtExpandMacroResponse { string expansion = 2; } +message LspExtOpenDocs { + uint64 project_id = 1; + uint64 buffer_id = 2; + Anchor position = 3; +} + +message LspExtOpenDocsResponse { + optional string web = 1; + optional string local = 2; +} + message LspExtSwitchSourceHeader { uint64 project_id = 1; uint64 buffer_id = 2; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index e143f24a47..2ec9f8bf55 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -314,6 +314,8 @@ messages!( (UsersResponse, Foreground), (LspExtExpandMacro, Background), (LspExtExpandMacroResponse, Background), + (LspExtOpenDocs, Background), + (LspExtOpenDocsResponse, Background), (SetRoomParticipantRole, Foreground), (BlameBuffer, Foreground), (BlameBufferResponse, Foreground), @@ -464,6 +466,7 @@ request_messages!( (UpdateProject, Ack), (UpdateWorktree, Ack), (LspExtExpandMacro, LspExtExpandMacroResponse), + (LspExtOpenDocs, LspExtOpenDocsResponse), (SetRoomParticipantRole, Ack), (BlameBuffer, BlameBufferResponse), (RejoinRemoteProjects, RejoinRemoteProjectsResponse), @@ -552,6 +555,7 @@ entity_messages!( UpdateWorktree, UpdateWorktreeSettings, LspExtExpandMacro, + LspExtOpenDocs, AdvertiseContexts, OpenContext, CreateContext, From 31566cb5a021d8351aa3af0e6d425642d6acef78 Mon Sep 17 00:00:00 2001 From: Isaac Donaldson <46387819+isaacdonaldson@users.noreply.github.com> Date: Sat, 16 Nov 2024 10:52:43 -0800 Subject: [PATCH 018/157] Add width setting for the file finder (#18682) This PR adds the ability to adjust the width of the file finder popup. I found when searching my projects the default width was not always wide enough and there was no option to change it. It allows values `small`, `medium` (default), `large`, `xlarge`, and `full` Release Notes: - Added a setting to adjust the width of the file finder modal Example Setting: ```json "file_finder": { "modal_width": "medium" }, ``` Screenshots can be found in the comments below. --- assets/settings/default.json | 16 ++++++- crates/file_finder/src/file_finder.rs | 9 +++- .../file_finder/src/file_finder_settings.rs | 48 +++++++++++++++++++ docs/src/configuring-zed.md | 8 ++++ 4 files changed, 78 insertions(+), 3 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 0413299fcb..c881c61d35 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -580,7 +580,21 @@ // Settings related to the file finder. "file_finder": { // Whether to show file icons in the file finder. - "file_icons": true + "file_icons": true, + // Width of the file finder modal. This setting can + // take four values. + // + // 1. Small width: + // "modal_width": "small", + // 2. Medium width (default): + // "modal_width": "medium", + // 3. Large width: + // "modal_width": "large", + // 4. Extra Large width: + // "modal_width": "xlarge" + // 5. Fullscreen width: + // "modal_width": "full" + "modal_width": "medium" }, // Whether or not to remove any trailing whitespace from lines of a buffer // before saving it. diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 286b154f38..f9c058de23 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -14,7 +14,7 @@ use file_finder_settings::FileFinderSettings; use file_icons::FileIcons; use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; use gpui::{ - actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, + actions, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, KeyContext, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView, }; @@ -257,9 +257,14 @@ impl FocusableView for FileFinder { impl Render for FileFinder { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let key_context = self.picker.read(cx).delegate.key_context(cx); + + let window_max_width: Pixels = cx.viewport_size().width; + let modal_choice = FileFinderSettings::get_global(cx).modal_width; + let width = modal_choice.calc_width(window_max_width); + v_flex() .key_context(key_context) - .w(rems(34.)) + .w(width) .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed)) .on_action(cx.listener(Self::handle_select_prev)) .on_action(cx.listener(Self::handle_open_menu)) diff --git a/crates/file_finder/src/file_finder_settings.rs b/crates/file_finder/src/file_finder_settings.rs index c02008c917..4379c8f543 100644 --- a/crates/file_finder/src/file_finder_settings.rs +++ b/crates/file_finder/src/file_finder_settings.rs @@ -2,10 +2,13 @@ use anyhow::Result; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; +use std::cmp; +use ui::Pixels; #[derive(Deserialize, Debug, Clone, Copy, PartialEq)] pub struct FileFinderSettings { pub file_icons: bool, + pub modal_width: FileFinderWidth, } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] @@ -14,6 +17,10 @@ pub struct FileFinderSettingsContent { /// /// Default: true pub file_icons: Option, + /// The width of the file finder modal. + /// + /// Default: "medium" + pub modal_width: Option, } impl Settings for FileFinderSettings { @@ -25,3 +32,44 @@ impl Settings for FileFinderSettings { sources.json_merge() } } + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum FileFinderWidth { + Small, + #[default] + Medium, + Large, + XLarge, + Full, +} + +impl FileFinderWidth { + const MIN_MODAL_WIDTH_PX: f32 = 384.; + + pub fn padding_px(&self) -> Pixels { + let padding_val = match self { + FileFinderWidth::Small => 1280., + FileFinderWidth::Medium => 1024., + FileFinderWidth::Large => 768., + FileFinderWidth::XLarge => 512., + FileFinderWidth::Full => 0., + }; + + Pixels(padding_val) + } + + pub fn calc_width(&self, window_width: Pixels) -> Pixels { + if self == &FileFinderWidth::Full { + return window_width; + } + + let min_modal_width_px = Pixels(FileFinderWidth::MIN_MODAL_WIDTH_PX); + + let padding_px = self.padding_px(); + let width_val = window_width - padding_px; + let finder_width = cmp::max(min_modal_width_px, width_val); + + finder_width + } +} diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index 3e713359c4..e6e6b662c0 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -1416,6 +1416,14 @@ Or to set a `socks5` proxy: `boolean` values +## File Finder + +### Modal Width + +- Description: Width of the file finder modal. Can take one of a few values: `small`, `medium`, `large`, `xlarge`, and `full`. +- Setting: `modal_width` +- Default: `medium` + ## Preferred Line Length - Description: The column at which to soft-wrap lines, for buffers where soft-wrap is enabled. From ee207ab77ecd61fad41bb6191e978898dbfae2db Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 17 Nov 2024 07:38:30 -0700 Subject: [PATCH 019/157] Map "feature upsell" events to the new "Noun Verbed" format (#20787) Release Notes: - N/A --- crates/collab/src/api/events.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 3b7a3b3900..053657a8d1 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -1520,7 +1520,22 @@ fn for_snowflake( properties["is_first_for_channel"] = json!(true); "App First Opened For Release Channel".to_string() } - _ => format!("Unknown App Event: {}", e.operation), + "feature upsell: toggle vim" => { + properties["source"] = json!("Feature Upsell"); + "Vim Mode Toggled".to_string() + } + _ => e + .operation + .strip_prefix("feature upsell: viewed docs (") + .and_then(|s| s.strip_suffix(')')) + .map_or_else( + || format!("Unknown App Event: {}", e.operation), + |docs_url| { + properties["url"] = json!(docs_url); + properties["source"] = json!("Feature Upsell"); + "Documentation Viewed".to_string() + }, + ), }; (event_type, properties) } From 59a355da74def3d022ec6a7237bd648ddd129ff2 Mon Sep 17 00:00:00 2001 From: moshyfawn Date: Mon, 18 Nov 2024 02:51:47 -0500 Subject: [PATCH 020/157] docs: Update Svelte extension link (#20804) Closes #20768 Release Notes: - N/A --- docs/src/languages/svelte.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/languages/svelte.md b/docs/src/languages/svelte.md index 157a57d43e..df7343d569 100644 --- a/docs/src/languages/svelte.md +++ b/docs/src/languages/svelte.md @@ -1,6 +1,6 @@ # Svelte -Svelte support is available through the [Svelte extension](https://github.com/zed-industries/zed/tree/main/extensions/svelte). +Svelte support is available through the [Svelte extension](https://github.com/zed-extensions/svelte). - Tree Sitter: [tree-sitter-grammars/tree-sitter-svelte](https://github.com/tree-sitter-grammars/tree-sitter-svelte) - Language Server: [sveltejs/language-tools](https://github.com/sveltejs/language-tools) From d92166f9f602b2904c96bf7d41cd3b58c2421ffb Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 18 Nov 2024 11:43:53 +0200 Subject: [PATCH 021/157] Revert "Use livekit's Rust SDK instead of their swift SDK (#13343)" (#20809) Issues found: * audio does not work well with various set-ups using USB * switching audio during initial join may leave the client with no audio at all * audio streaming is done on the main thread, beachballing certain set-ups * worse screenshare quality (seems that there's no dynamic scaling anymore, compared to the Swift SDK) This reverts commit 1235d0808ed7697874085fc8528f22aec988c85e. Release Notes: - N/A --- .cargo/config.toml | 6 - Cargo.lock | 2330 ++++++----------- Cargo.toml | 6 - crates/call/Cargo.toml | 1 - crates/call/src/call.rs | 9 - crates/call/src/participant.rs | 28 +- crates/call/src/room.rs | 814 +++--- crates/collab/src/tests.rs | 3 - .../collab/src/tests/channel_guest_tests.rs | 24 +- crates/collab/src/tests/following_tests.rs | 13 +- crates/collab/src/tests/integration_tests.rs | 27 +- crates/collab/src/tests/test_server.rs | 6 +- crates/collab_ui/src/collab_panel.rs | 5 +- crates/gpui/build.rs | 1 - crates/gpui/src/app.rs | 11 +- crates/gpui/src/app/test_context.rs | 10 +- crates/gpui/src/geometry.rs | 5 - crates/gpui/src/platform.rs | 26 - crates/gpui/src/platform/linux.rs | 2 - crates/gpui/src/platform/linux/platform.rs | 12 +- crates/gpui/src/platform/mac.rs | 5 - crates/gpui/src/platform/mac/platform.rs | 14 +- .../gpui/src/platform/mac/screen_capture.rs | 239 -- crates/gpui/src/platform/test.rs | 2 - crates/gpui/src/platform/test/platform.rs | 58 +- crates/gpui/src/platform/windows.rs | 2 - crates/gpui/src/platform/windows/platform.rs | 8 - crates/http_client/Cargo.toml | 2 +- crates/live_kit_client/Cargo.toml | 29 +- .../LiveKitBridge/Package.resolved | 52 + .../LiveKitBridge/Package.swift | 27 + .../live_kit_client/LiveKitBridge/README.md | 3 + .../Sources/LiveKitBridge/LiveKitBridge.swift | 383 +++ crates/live_kit_client/build.rs | 185 ++ crates/live_kit_client/examples/test_app.rs | 520 +--- crates/live_kit_client/src/live_kit_client.rs | 406 +-- crates/live_kit_client/src/prod.rs | 981 +++++++ .../src/remote_video_track_view.rs | 61 - crates/live_kit_client/src/test.rs | 865 +++--- .../live_kit_client/src/test/participant.rs | 111 - .../live_kit_client/src/test/publication.rs | 116 - crates/live_kit_client/src/test/track.rs | 201 -- crates/live_kit_client/src/test/webrtc.rs | 136 - crates/media/Cargo.toml | 1 - crates/media/src/media.rs | 11 +- crates/title_bar/src/collab.rs | 65 +- crates/workspace/src/shared_screen.rs | 52 +- crates/workspace/src/workspace.rs | 13 +- 48 files changed, 3599 insertions(+), 4288 deletions(-) delete mode 100644 crates/gpui/src/platform/mac/screen_capture.rs create mode 100644 crates/live_kit_client/LiveKitBridge/Package.resolved create mode 100644 crates/live_kit_client/LiveKitBridge/Package.swift create mode 100644 crates/live_kit_client/LiveKitBridge/README.md create mode 100644 crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift create mode 100644 crates/live_kit_client/build.rs create mode 100644 crates/live_kit_client/src/prod.rs delete mode 100644 crates/live_kit_client/src/remote_video_track_view.rs delete mode 100644 crates/live_kit_client/src/test/participant.rs delete mode 100644 crates/live_kit_client/src/test/publication.rs delete mode 100644 crates/live_kit_client/src/test/track.rs delete mode 100644 crates/live_kit_client/src/test/webrtc.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 043adf6b30..a657ae61b9 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -13,12 +13,6 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"] linker = "clang" rustflags = ["-C", "link-arg=-fuse-ld=mold"] -[target.aarch64-apple-darwin] -rustflags = ["-C", "link-args=-Objc -all_load"] - -[target.x86_64-apple-darwin] -rustflags = ["-C", "link-args=-Objc -all_load"] - # This cfg will reduce the size of `windows::core::Error` from 16 bytes to 4 bytes [target.'cfg(target_os = "windows")'] rustflags = ["--cfg", "windows_slim_errors"] diff --git a/Cargo.lock b/Cargo.lock index 440002cb24..1b735b431d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ dependencies = [ "auto_update", "editor", "extension_host", - "futures 0.3.31", + "futures 0.3.30", "gpui", "language", "lsp", @@ -23,13 +23,19 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.2" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ - "gimli 0.31.1", + "gimli 0.31.0", ] +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "adler2" version = "2.0.0" @@ -94,8 +100,8 @@ dependencies = [ "miow", "parking_lot", "piper", - "polling 3.7.4", - "regex-automata 0.4.9", + "polling 3.7.3", + "regex-automata 0.4.7", "rustix-openpty", "serde", "signal-hook", @@ -118,9 +124,9 @@ checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alsa" @@ -186,9 +192,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -201,36 +207,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -239,13 +245,13 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", - "futures 0.3.31", + "futures 0.3.30", "http_client", "schemars", "serde", "serde_json", "strum 0.25.0", - "thiserror 1.0.69", + "thiserror", "util", ] @@ -272,9 +278,9 @@ dependencies = [ [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" [[package]] name = "arg_enum_proc_macro" @@ -295,9 +301,9 @@ checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" [[package]] name = "arrayref" -version = "0.3.9" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" [[package]] name = "arrayvec" @@ -390,7 +396,7 @@ dependencies = [ "env_logger 0.11.5", "feature_flags", "fs", - "futures 0.3.31", + "futures 0.3.30", "fuzzy", "globset", "gpui", @@ -457,7 +463,7 @@ dependencies = [ "collections", "derive_more", "extension", - "futures 0.3.31", + "futures 0.3.30", "gpui", "language", "language_model", @@ -567,14 +573,14 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.2.0", - "futures-lite 2.5.0", + "fastrand 2.1.1", + "futures-lite 2.3.0", "slab", ] @@ -598,7 +604,7 @@ checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ "async-lock 3.4.0", "blocking", - "futures-lite 2.5.0", + "futures-lite 2.3.0", ] [[package]] @@ -609,10 +615,10 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.3.1", "async-executor", - "async-io 2.4.0", + "async-io 2.3.4", "async-lock 3.4.0", "blocking", - "futures-lite 2.5.0", + "futures-lite 2.3.0", "once_cell", ] @@ -638,18 +644,18 @@ dependencies = [ [[package]] name = "async-io" -version = "2.4.0" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ "async-lock 3.4.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.5.0", + "futures-lite 2.3.0", "parking", - "polling 3.7.4", - "rustix 0.38.40", + "polling 3.7.3", + "rustix 0.38.35", "slab", "tracing", "windows-sys 0.59.0", @@ -683,7 +689,7 @@ checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" dependencies = [ "futures-util", "native-tls", - "thiserror 1.0.69", + "thiserror", "url", ] @@ -704,9 +710,9 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ - "async-io 2.4.0", + "async-io 2.3.4", "blocking", - "futures-lite 2.5.0", + "futures-lite 2.3.0", ] [[package]] @@ -714,7 +720,7 @@ name = "async-pipe" version = "0.1.3" source = "git+https://github.com/zed-industries/async-pipe-rs?rev=82d00a04211cf4e1236029aa03e6b6ce2a74c553#82d00a04211cf4e1236029aa03e6b6ce2a74c553" dependencies = [ - "futures 0.3.31", + "futures 0.3.30", "log", ] @@ -731,27 +737,28 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.40", + "rustix 0.38.35", "windows-sys 0.48.0", ] [[package]] name = "async-process" -version = "2.3.0" +version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" dependencies = [ "async-channel 2.3.1", - "async-io 2.4.0", + "async-io 2.3.4", "async-lock 3.4.0", "async-signal", "async-task", "blocking", "cfg-if", "event-listener 5.3.1", - "futures-lite 2.5.0", - "rustix 0.38.40", + "futures-lite 2.3.0", + "rustix 0.38.35", "tracing", + "windows-sys 0.59.0", ] [[package]] @@ -782,13 +789,13 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.4.0", + "async-io 2.3.4", "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.40", + "rustix 0.38.35", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -796,21 +803,21 @@ dependencies = [ [[package]] name = "async-std" -version = "1.13.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", - "async-io 2.4.0", - "async-lock 3.4.0", - "async-process 2.3.0", + "async-io 1.13.0", + "async-lock 2.8.0", + "async-process 1.8.1", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 2.5.0", + "futures-lite 1.13.0", "gloo-timers", "kv-log-macro", "log", @@ -824,9 +831,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", @@ -835,9 +842,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", @@ -860,7 +867,7 @@ dependencies = [ "serde_qs 0.10.1", "smart-default", "smol_str", - "thiserror 1.0.69", + "thiserror", "tokio", ] @@ -908,22 +915,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "async-tungstenite" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" -dependencies = [ - "async-native-tls", - "async-std", - "async-tls", - "futures-io", - "futures-util", - "log", - "pin-project-lite", - "tungstenite 0.21.0", -] - [[package]] name = "async-tungstenite" version = "0.28.0" @@ -956,9 +947,9 @@ checksum = "00b9f7252833d5ed4b00aa9604b563529dd5e11de9c23615de2dcdf91eb87b52" dependencies = [ "async-compression", "crc32fast", - "futures-lite 2.5.0", + "futures-lite 2.3.0", "pin-project", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -967,7 +958,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "futures-sink", "futures-util", "memchr", @@ -1037,9 +1028,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "av1-grain" @@ -1057,18 +1048,18 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" +checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" dependencies = [ "arrayvec", ] [[package]] name = "aws-config" -version = "1.5.10" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" +checksum = "4e95816a168520d72c0e7680c405a5a8c1fb6a035b4bc4b9d7b0de8e1a941697" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1082,8 +1073,8 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.8.0", - "fastrand 2.2.0", + "bytes 1.7.2", + "fastrand 2.1.1", "hex", "http 0.2.12", "ring", @@ -1121,8 +1112,8 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.8.0", - "fastrand 2.2.0", + "bytes 1.7.2", + "fastrand 2.1.1", "http 0.2.12", "http-body 0.4.6", "once_cell", @@ -1147,7 +1138,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.8.0", + "bytes 1.7.2", "http 0.2.12", "once_cell", "regex-lite", @@ -1156,10 +1147,11 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.61.0" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e531658a0397d22365dfe26c3e1c0c8448bf6a3a2d8a098ded802f2b1261615" +checksum = "cca49303c05d2a740b8a4552fac63a4db6ead84f7e7eeed04761fd3014c26f25" dependencies = [ + "ahash 0.8.11", "aws-credential-types", "aws-runtime", "aws-sigv4", @@ -1173,8 +1165,8 @@ dependencies = [ "aws-smithy-types", "aws-smithy-xml", "aws-types", - "bytes 1.8.0", - "fastrand 2.2.0", + "bytes 1.7.2", + "fastrand 2.1.1", "hex", "hmac", "http 0.2.12", @@ -1190,9 +1182,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.49.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4" +checksum = "e5879bec6e74b648ce12f6085e7245417bc5f6d672781028384d2e494be3eb6d" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1203,7 +1195,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.8.0", + "bytes 1.7.2", "http 0.2.12", "once_cell", "regex-lite", @@ -1212,9 +1204,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.50.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" +checksum = "4ef4cd9362f638c22a3b959fd8df292e7e47fdf170270f86246b97109b5f2f7d" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1225,7 +1217,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.8.0", + "bytes 1.7.2", "http 0.2.12", "once_cell", "regex-lite", @@ -1234,9 +1226,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.50.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" +checksum = "0b1e2735d2ab28b35ecbb5496c9d41857f52a0d6a0075bbf6a8af306045ea6f6" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1266,7 +1258,7 @@ dependencies = [ "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.8.0", + "bytes 1.7.2", "crypto-bigint 0.5.5", "form_urlencoded", "hex", @@ -1297,13 +1289,13 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.60.13" +version = "0.60.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795" +checksum = "598b1689d001c4d4dc3cb386adb07d37786783aee3ac4b324bcadac116bf3d23" dependencies = [ "aws-smithy-http", "aws-smithy-types", - "bytes 1.8.0", + "bytes 1.7.2", "crc32c", "crc32fast", "hex", @@ -1323,7 +1315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cef7d0a272725f87e51ba2bf89f8c21e4df61b9e49ae1ac367a6d69916ef7c90" dependencies = [ "aws-smithy-types", - "bytes 1.8.0", + "bytes 1.7.2", "crc32fast", ] @@ -1336,7 +1328,7 @@ dependencies = [ "aws-smithy-eventstream", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.8.0", + "bytes 1.7.2", "bytes-utils", "futures-core", "http 0.2.12", @@ -1377,8 +1369,8 @@ dependencies = [ "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.8.0", - "fastrand 2.2.0", + "bytes 1.7.2", + "fastrand 2.1.1", "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", @@ -1402,7 +1394,7 @@ checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" dependencies = [ "aws-smithy-async", "aws-smithy-types", - "bytes 1.8.0", + "bytes 1.7.2", "http 0.2.12", "http 1.1.0", "pin-project-lite", @@ -1418,7 +1410,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" dependencies = [ "base64-simd", - "bytes 1.8.0", + "bytes 1.7.2", "bytes-utils", "futures-core", "http 0.2.12", @@ -1439,9 +1431,9 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.9" +version = "0.60.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +checksum = "d123fbc2a4adc3c301652ba8e149bf4bc1d1725affb9784eb20c953ace06bf55" dependencies = [ "xmlparser", ] @@ -1470,7 +1462,7 @@ dependencies = [ "axum-core", "base64 0.21.7", "bitflags 1.3.2", - "bytes 1.8.0", + "bytes 1.7.2", "futures-util", "headers", "http 0.2.12", @@ -1503,7 +1495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ "async-trait", - "bytes 1.8.0", + "bytes 1.7.2", "futures-util", "http 0.2.12", "http-body 0.4.6", @@ -1520,7 +1512,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9a320103719de37b7b4da4c8eb629d4573f6bcfd3dfe80d3208806895ccf81d" dependencies = [ "axum", - "bytes 1.8.0", + "bytes 1.7.2", "futures-util", "http 0.2.12", "mime", @@ -1543,7 +1535,7 @@ dependencies = [ "addr2line", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.8.0", "object", "rustc-demangle", "windows-targets 0.52.6", @@ -1591,9 +1583,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bigdecimal" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f850665a0385e070b64c38d2354e6c104c8479c59868d1e48a0c13ee2c7a1c1" +checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee" dependencies = [ "autocfg", "libm", @@ -1612,6 +1604,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.87", +] + [[package]] name = "bindgen" version = "0.70.1" @@ -1700,9 +1712,9 @@ dependencies = [ [[package]] name = "bitstream-io" -version = "2.6.0" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" +checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" [[package]] name = "bitvec" @@ -1777,7 +1789,7 @@ dependencies = [ "arrayvec", "cc", "cfg-if", - "constant_time_eq 0.3.1", + "constant_time_eq", ] [[package]] @@ -1813,15 +1825,15 @@ dependencies = [ "async-channel 2.3.1", "async-task", "futures-io", - "futures-lite 2.5.0", + "futures-lite 2.3.0", "piper", ] [[package]] name = "borsh" -version = "1.5.3" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" dependencies = [ "borsh-derive", "cfg_aliases 0.2.1", @@ -1829,15 +1841,16 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.3" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.87", + "syn_derive", ] [[package]] @@ -1855,20 +1868,20 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.7", "serde", ] [[package]] name = "built" -version = "0.7.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" +checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" [[package]] name = "bumpalo" @@ -1906,18 +1919,18 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.19.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" +checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" dependencies = [ "proc-macro2", "quote", @@ -1948,9 +1961,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.8.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "bytes-utils" @@ -1958,31 +1971,10 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "either", ] -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - [[package]] name = "call" version = "0.1.0" @@ -1991,9 +1983,8 @@ dependencies = [ "audio", "client", "collections", - "feature_flags", "fs", - "futures 0.3.31", + "futures 0.3.30", "gpui", "http_client", "language", @@ -2016,10 +2007,10 @@ checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ "bitflags 2.6.0", "log", - "polling 3.7.4", - "rustix 0.38.40", + "polling 3.7.3", + "rustix 0.38.35", "slab", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -2029,7 +2020,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix 0.38.40", + "rustix 0.38.35", "wayland-backend", "wayland-client", ] @@ -2045,9 +2036,9 @@ dependencies = [ [[package]] name = "cap-fs-ext" -version = "3.4.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16619ada836f12897a72011fe99b03f0025b87a8dbbea4f3c9f89b458a23bf3" +checksum = "eb23061fc1c4ead4e45ca713080fe768e6234e959f5a5c399c39eb41aa34e56e" dependencies = [ "cap-primitives", "cap-std", @@ -2057,21 +2048,21 @@ dependencies = [ [[package]] name = "cap-net-ext" -version = "3.4.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710b0eb776410a22c89a98f2f80b2187c2ac3a8206b99f3412332e63c9b09de0" +checksum = "f83ae11f116bcbafc5327c6af250341db96b5930046732e1905f7dc65887e0e1" dependencies = [ "cap-primitives", "cap-std", - "rustix 0.38.40", + "rustix 0.38.35", "smallvec", ] [[package]] name = "cap-primitives" -version = "3.4.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fa6c3f9773feab88d844aa50035a33fb6e7e7426105d2f4bb7aadc42a5f89a" +checksum = "6d00bd8d26c4270d950eaaa837387964a2089a1c3c349a690a1fa03221d29531" dependencies = [ "ambient-authority", "fs-set-times", @@ -2079,16 +2070,16 @@ dependencies = [ "io-lifetimes 2.0.3", "ipnet", "maybe-owned", - "rustix 0.38.40", + "rustix 0.38.35", "windows-sys 0.52.0", "winx", ] [[package]] name = "cap-rand" -version = "3.4.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53774d49369892b70184f8312e50c1b87edccb376691de4485b0ff554b27c36c" +checksum = "dbcb16a619d8b8211ed61f42bd290d2a1ac71277a69cf8417ec0996fa92f5211" dependencies = [ "ambient-authority", "rand 0.8.5", @@ -2096,27 +2087,27 @@ dependencies = [ [[package]] name = "cap-std" -version = "3.4.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f71b70818556b4fe2a10c7c30baac3f5f45e973f49fc2673d7c75c39d0baf5b" +checksum = "19eb8e3d71996828751c1ed3908a439639752ac6bdc874e41469ef7fc15fbd7f" dependencies = [ "cap-primitives", "io-extras", "io-lifetimes 2.0.3", - "rustix 0.38.40", + "rustix 0.38.35", ] [[package]] name = "cap-time-ext" -version = "3.4.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69dd48afa2363f746c93f961c211f6f099fb594a3446b8097bc5f79db51b6816" +checksum = "61142dc51e25b7acc970ca578ce2c3695eac22bbba46c1073f5f583e78957725" dependencies = [ "ambient-authority", "cap-primitives", "iana-time-zone", "once_cell", - "rustix 0.38.40", + "rustix 0.38.35", "winx", ] @@ -2140,7 +2131,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -2175,7 +2166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" dependencies = [ "heck 0.4.1", - "indexmap 2.6.0", + "indexmap 2.4.0", "log", "proc-macro2", "quote", @@ -2188,9 +2179,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.1" +version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" dependencies = [ "jobserver", "libc", @@ -2248,7 +2239,7 @@ dependencies = [ "client", "clock", "collections", - "futures 0.3.31", + "futures 0.3.30", "gpui", "http_client", "language", @@ -2358,9 +2349,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.38" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" +checksum = "6d7db6eca8c205649e8d3ccd05aa5042b1800a784e56bc7c43524fde8abbfa9b" dependencies = [ "clap", ] @@ -2379,9 +2370,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "cli" @@ -2412,17 +2403,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0875e527e299fc5f4faba42870bf199a39ab0bb2dbba1b8aef0a2151451130f" dependencies = [ "bstr", - "bytes 1.8.0", + "bytes 1.7.2", "clickhouse-derive", "clickhouse-rs-cityhash-sys", - "futures 0.3.31", + "futures 0.3.30", "hyper 0.14.31", "hyper-tls", "lz4", "sealed", "serde", "static_assertions", - "thiserror 1.0.69", + "thiserror", "tokio", "url", ] @@ -2455,13 +2446,13 @@ dependencies = [ "anyhow", "async-native-tls", "async-recursion 0.3.2", - "async-tungstenite 0.28.0", + "async-tungstenite", "chrono", "clock", "cocoa 0.26.0", "collections", "feature_flags", - "futures 0.3.31", + "futures 0.3.30", "gpui", "http_client", "log", @@ -2483,7 +2474,7 @@ dependencies = [ "sysinfo", "telemetry_events", "text", - "thiserror 1.0.69", + "thiserror", "time", "tiny_http", "tokio-socks", @@ -2588,7 +2579,7 @@ dependencies = [ "assistant", "async-stripe", "async-trait", - "async-tungstenite 0.28.0", + "async-tungstenite", "audio", "aws-config", "aws-sdk-kinesis", @@ -2606,14 +2597,14 @@ dependencies = [ "collections", "context_servers", "ctor", - "dashmap 6.1.0", + "dashmap 6.0.1", "derive_more", "editor", "env_logger 0.11.5", "envy", "file_finder", "fs", - "futures 0.3.31", + "futures 0.3.30", "git", "git_hosting_providers", "google_ai", @@ -2639,7 +2630,7 @@ dependencies = [ "pretty_assertions", "project", "prometheus", - "prost 0.9.0", + "prost", "rand 0.8.5", "recent_projects", "release_channel", @@ -2666,7 +2657,7 @@ dependencies = [ "telemetry_events", "text", "theme", - "thiserror 1.0.69", + "thiserror", "time", "tokio", "toml 0.8.19", @@ -2694,7 +2685,7 @@ dependencies = [ "db", "editor", "emojis", - "futures 0.3.31", + "futures 0.3.30", "fuzzy", "gpui", "http_client", @@ -2741,9 +2732,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "combine" @@ -2751,7 +2742,7 @@ version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "memchr", ] @@ -2840,12 +2831,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - [[package]] name = "constant_time_eq" version = "0.3.1" @@ -2859,7 +2844,7 @@ dependencies = [ "anyhow", "collections", "command_palette_hooks", - "futures 0.3.31", + "futures 0.3.30", "gpui", "log", "parking_lot", @@ -2904,7 +2889,7 @@ dependencies = [ "command_palette_hooks", "editor", "fs", - "futures 0.3.31", + "futures 0.3.30", "gpui", "http_client", "indoc", @@ -3037,11 +3022,11 @@ dependencies = [ [[package]] name = "coreaudio-sys" -version = "0.2.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce857aa0b77d77287acc1ac3e37a05a8c95a2af3647d23b15f263bdaeb7562b" +checksum = "7f01585027057ff5f0a5bf276174ae4c1594a2c5bde93d5f46a016d76270f5a9" dependencies = [ - "bindgen", + "bindgen 0.69.4", ] [[package]] @@ -3069,7 +3054,8 @@ dependencies = [ [[package]] name = "cpal" version = "0.15.3" -source = "git+https://github.com/zed-industries/cpal?rev=fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50#fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" dependencies = [ "alsa", "core-foundation-sys", @@ -3099,9 +3085,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] @@ -3405,50 +3391,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" -[[package]] -name = "cxx" -version = "1.0.130" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c042a0ba58aaff55299632834d1ea53ceff73d62373f62c9ae60890ad1b942" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.130" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45dc1c88d0fdac57518a9b1f6c4f4fb2aca8f3c30c0d03d7d8518b47ca0bcea6" -dependencies = [ - "cc", - "codespan-reporting", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.87", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.130" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa7ed7d30b289e2592cc55bc2ccd89803a63c913e008e6eb59f06cddf45bb52f" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.130" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c465d22de46b851c04630a5fc749a26005b263632ed2e0d9cc81518ead78d" -dependencies = [ - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.87", -] - [[package]] name = "dashmap" version = "5.5.3" @@ -3464,9 +3406,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.1.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" dependencies = [ "cfg-if", "crossbeam-utils", @@ -3619,7 +3561,7 @@ dependencies = [ "fuzzy-matcher", "shell-words", "tempfile", - "thiserror 1.0.69", + "thiserror", "zeroize", ] @@ -3682,17 +3624,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "dlib" version = "0.5.2" @@ -3739,9 +3670,9 @@ dependencies = [ [[package]] name = "dwrote" -version = "0.11.2" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70182709525a3632b2ba96b6569225467b18ecb4a77f46d255f713a6bebf05fd" +checksum = "2da3498378ed373237bdef1eddcc64e7be2d3ba4841f4c22a998e81cadeea83c" dependencies = [ "lazy_static", "libc", @@ -3790,7 +3721,7 @@ dependencies = [ "emojis", "env_logger 0.11.5", "file_icons", - "futures 0.3.31", + "futures 0.3.30", "fuzzy", "git", "gpui", @@ -3836,6 +3767,18 @@ dependencies = [ "workspace", ] +[[package]] +name = "educe" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4bd92664bf78c4d3dba9b7cdafce6fa15b13ed3ed16175218196942e99168a8" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "either" version = "1.13.0" @@ -3879,9 +3822,9 @@ dependencies = [ [[package]] name = "embed-resource" -version = "2.5.1" +version = "2.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68b6f9f63a0b6a38bc447d4ce84e2b388f3ec95c99c641c8ff0dd3ef89a6379" +checksum = "4edcacde9351c33139a41e3c97eb2334351a81a2791bebb0b243df837128f602" dependencies = [ "cc", "memchr", @@ -3920,9 +3863,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -3933,6 +3876,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "enumflags2" version = "0.7.10" @@ -4154,14 +4117,15 @@ dependencies = [ [[package]] name = "exr" -version = "1.73.0" +version = "1.72.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" dependencies = [ "bit_field", + "flume", "half", "lebe", - "miniz_oxide", + "miniz_oxide 0.7.4", "rayon-core", "smallvec", "zune-inflate", @@ -4177,7 +4141,7 @@ dependencies = [ "async-trait", "collections", "fs", - "futures 0.3.31", + "futures 0.3.30", "gpui", "http_client", "language", @@ -4229,7 +4193,7 @@ dependencies = [ "env_logger 0.11.5", "extension", "fs", - "futures 0.3.31", + "futures 0.3.30", "gpui", "http_client", "language", @@ -4318,8 +4282,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ "bit-set 0.8.0", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -4339,9 +4303,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fd-lock" @@ -4350,15 +4314,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" dependencies = [ "cfg-if", - "rustix 0.38.40", + "rustix 0.38.35", "windows-sys 0.52.0", ] [[package]] name = "fdeflate" -version = "0.3.6" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" dependencies = [ "simd-adler32", ] @@ -4367,7 +4331,7 @@ dependencies = [ name = "feature_flags" version = "0.1.0" dependencies = [ - "futures 0.3.31", + "futures 0.3.30", "gpui", ] @@ -4380,7 +4344,7 @@ dependencies = [ "client", "db", "editor", - "futures 0.3.31", + "futures 0.3.30", "gpui", "http_client", "human_bytes", @@ -4421,7 +4385,7 @@ dependencies = [ "editor", "env_logger 0.11.5", "file_icons", - "futures 0.3.31", + "futures 0.3.30", "fuzzy", "gpui", "language", @@ -4459,7 +4423,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" dependencies = [ "libc", - "thiserror 1.0.69", + "thiserror", "winapi", ] @@ -4483,12 +4447,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.35" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -4521,12 +4485,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" - [[package]] name = "font-kit" version = "0.14.1" @@ -4553,9 +4511,9 @@ dependencies = [ [[package]] name = "font-types" -version = "0.7.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3971f9a5ca983419cdc386941ba3b9e1feba01a0ab888adf78739feb2798492" +checksum = "8f0189ccb084f77c5523e08288d418cbaa09c451a08515678a0aa265df9a8b60" dependencies = [ "bytemuck", ] @@ -4665,7 +4623,7 @@ dependencies = [ "cocoa 0.26.0", "collections", "fsevent", - "futures 0.3.31", + "futures 0.3.30", "git", "git2", "gpui", @@ -4692,20 +4650,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "033b337d725b97690d86893f9de22b67b80dcc4e9ad815f348254c38119db8fb" dependencies = [ "io-lifetimes 2.0.3", - "rustix 0.38.40", + "rustix 0.38.35", "windows-sys 0.52.0", ] -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "fsevent" version = "0.1.0" @@ -4759,9 +4707,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -4778,16 +4726,16 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f444c45a1cb86f2a7e301469fd50a82084a60dadc25d94529a8312276ecb71a" dependencies = [ - "futures 0.3.31", + "futures 0.3.30", "futures-timer", "pin-utils", ] [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -4795,15 +4743,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -4823,9 +4771,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" @@ -4844,11 +4792,11 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.5.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.2.0", + "fastrand 2.1.1", "futures-core", "futures-io", "parking", @@ -4857,9 +4805,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", @@ -4868,15 +4816,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" @@ -4886,9 +4834,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures 0.1.31", "futures-channel", @@ -4982,15 +4930,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" dependencies = [ "fallible-iterator", - "indexmap 2.6.0", + "indexmap 2.4.0", "stable_deref_trait", ] [[package]] name = "gimli" -version = "0.31.1" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "git" @@ -5039,7 +4987,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "futures 0.3.31", + "futures 0.3.30", "git", "gpui", "http_client", @@ -5067,15 +5015,15 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] name = "gloo-timers" -version = "0.3.0" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" dependencies = [ "futures-channel", "futures-core", @@ -5085,9 +5033,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.14.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" +checksum = "f865cbd94bd355b89611211e49508da98a1fce0ad755c1e8448fb96711b24528" dependencies = [ "js-sys", "slotmap", @@ -5125,7 +5073,7 @@ name = "google_ai" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.31", + "futures 0.3.30", "http_client", "schemars", "serde", @@ -5172,7 +5120,7 @@ dependencies = [ "ashpd", "async-task", "backtrace", - "bindgen", + "bindgen 0.70.1", "blade-graphics", "blade-macros", "blade-util", @@ -5197,7 +5145,7 @@ dependencies = [ "flume", "font-kit", "foreign-types 0.5.0", - "futures 0.3.31", + "futures 0.3.30", "gpui_macros", "http_client", "image", @@ -5231,7 +5179,7 @@ dependencies = [ "strum 0.25.0", "sum_tree", "taffy", - "thiserror 1.0.69", + "thiserror", "unicode-segmentation", "usvg", "util", @@ -5282,13 +5230,13 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "fnv", "futures-core", "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.6.0", + "indexmap 2.4.0", "slab", "tokio", "tokio-util", @@ -5302,12 +5250,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", - "bytes 1.8.0", + "bytes 1.7.2", "fnv", "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.6.0", + "indexmap 2.4.0", "slab", "tokio", "tokio-util", @@ -5335,7 +5283,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -5350,7 +5298,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -5382,17 +5330,6 @@ dependencies = [ "serde", ] -[[package]] -name = "hashbrown" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - [[package]] name = "hashlink" version = "0.8.4" @@ -5418,7 +5355,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ "base64 0.21.7", - "bytes 1.8.0", + "bytes 1.7.2", "headers-core", "http 0.2.12", "httpdate", @@ -5597,7 +5534,7 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "fnv", "itoa", ] @@ -5608,7 +5545,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "fnv", "itoa", ] @@ -5619,7 +5556,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "http 0.2.12", "pin-project-lite", ] @@ -5630,7 +5567,7 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "http 1.1.0", ] @@ -5640,7 +5577,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "futures-util", "http 1.1.0", "http-body 1.0.1", @@ -5679,9 +5616,9 @@ name = "http_client" version = "0.1.0" dependencies = [ "anyhow", - "bytes 1.8.0", + "bytes 1.7.2", "derive_more", - "futures 0.3.31", + "futures 0.3.30", "http 1.1.0", "log", "serde", @@ -5691,9 +5628,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -5719,7 +5656,7 @@ version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "futures-channel", "futures-core", "futures-util", @@ -5739,11 +5676,11 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "futures-channel", "futures-util", "h2 0.4.6", @@ -5781,9 +5718,9 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.5.0", + "hyper 1.4.1", "hyper-util", - "rustls 0.23.16", + "rustls 0.23.13", "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", @@ -5797,7 +5734,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "hyper 0.14.31", "native-tls", "tokio", @@ -5806,16 +5743,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.5.0", + "hyper 1.4.1", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -5825,9 +5762,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -5846,124 +5783,6 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "id-arena" version = "2.2.1" @@ -5972,23 +5791,12 @@ checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" [[package]] name = "idna" -version = "1.0.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -6001,7 +5809,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.7", "same-file", "walkdir", "winapi-util", @@ -6009,9 +5817,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.5" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" dependencies = [ "bytemuck", "byteorder-lite", @@ -6032,9 +5840,9 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.2.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f" +checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" dependencies = [ "byteorder-lite", "quick-error", @@ -6064,9 +5872,9 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] name = "imgref" -version = "1.11.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" +checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" [[package]] name = "indexed_docs" @@ -6079,7 +5887,7 @@ dependencies = [ "derive_more", "extension", "fs", - "futures 0.3.31", + "futures 0.3.30", "fuzzy", "gpui", "heed", @@ -6108,12 +5916,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.14.5", "serde", ] @@ -6148,7 +5956,7 @@ dependencies = [ "copilot", "editor", "fs", - "futures 0.3.31", + "futures 0.3.30", "gpui", "indoc", "language", @@ -6227,9 +6035,9 @@ dependencies = [ [[package]] name = "io-extras" -version = "0.18.3" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d45fd7584f9b67ac37bc041212d06bfac0700b36456b05890d36a3b626260eb" +checksum = "c9f046b9af244f13b3bd939f55d16830ac3a201e8a9ba9661bfcb03e2be72b9b" dependencies = [ "io-lifetimes 2.0.3", "windows-sys 0.52.0", @@ -6282,9 +6090,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-docker" @@ -6331,15 +6139,6 @@ dependencies = [ "either", ] -[[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.12.1" @@ -6375,7 +6174,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror 1.0.69", + "thiserror", "walkdir", "windows-sys 0.45.0", ] @@ -6419,9 +6218,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -6450,7 +6249,7 @@ dependencies = [ "anyhow", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", "uuid", ] @@ -6486,9 +6285,9 @@ dependencies = [ [[package]] name = "kurbo" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" +checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" dependencies = [ "arrayvec", "smallvec", @@ -6515,7 +6314,7 @@ dependencies = [ "ctor", "ec4rs", "env_logger 0.11.5", - "futures 0.3.31", + "futures 0.3.30", "fuzzy", "git", "globset", @@ -6573,7 +6372,7 @@ dependencies = [ "editor", "env_logger 0.11.5", "feature_flags", - "futures 0.3.31", + "futures 0.3.30", "google_ai", "gpui", "http_client", @@ -6597,7 +6396,7 @@ dependencies = [ "telemetry_events", "text", "theme", - "thiserror 1.0.69", + "thiserror", "tiktoken-rs", "ui", "unindent", @@ -6630,7 +6429,7 @@ dependencies = [ "copilot", "editor", "env_logger 0.11.5", - "futures 0.3.31", + "futures 0.3.30", "gpui", "itertools 0.13.0", "language", @@ -6656,7 +6455,7 @@ dependencies = [ "async-tar", "async-trait", "collections", - "futures 0.3.31", + "futures 0.3.30", "gpui", "http_client", "language", @@ -6713,6 +6512,12 @@ dependencies = [ "spin", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "leb128" version = "0.2.5" @@ -6743,12 +6548,13 @@ dependencies = [ [[package]] name = "libfuzzer-sys" -version = "0.4.8" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" dependencies = [ "arbitrary", "cc", + "once_cell", ] [[package]] @@ -6775,9 +6581,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.11" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libmimalloc-sys" @@ -6797,7 +6603,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.3", ] [[package]] @@ -6811,29 +6617,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libwebrtc" -version = "0.3.7" -source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" -dependencies = [ - "cxx", - "jni", - "js-sys", - "lazy_static", - "livekit-protocol", - "livekit-runtime", - "log", - "parking_lot", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webrtc-sys", -] - [[package]] name = "libz-sys" version = "1.1.20" @@ -6846,15 +6629,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "link-cplusplus" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" -dependencies = [ - "cc", -] - [[package]] name = "linkify" version = "0.10.0" @@ -6896,27 +6670,18 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" -[[package]] -name = "litemap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" - [[package]] name = "live_kit_client" version = "0.1.0" dependencies = [ "anyhow", + "async-broadcast", "async-trait", "collections", "core-foundation 0.9.4", - "cpal", - "futures 0.3.31", + "futures 0.3.30", "gpui", - "http 0.2.12", - "http_client", "live_kit_server", - "livekit", "log", "media", "nanoid", @@ -6926,7 +6691,6 @@ dependencies = [ "serde_json", "sha2", "simplelog", - "util", ] [[package]] @@ -6937,88 +6701,13 @@ dependencies = [ "async-trait", "jsonwebtoken", "log", - "prost 0.9.0", - "prost-build 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-build", + "prost-types", "reqwest 0.12.8", "serde", ] -[[package]] -name = "livekit" -version = "0.7.0" -source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" -dependencies = [ - "chrono", - "futures-util", - "lazy_static", - "libwebrtc", - "livekit-api", - "livekit-protocol", - "livekit-runtime", - "log", - "parking_lot", - "prost 0.12.6", - "semver", - "serde", - "serde_json", - "thiserror 1.0.69", - "tokio", -] - -[[package]] -name = "livekit-api" -version = "0.4.1" -source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" -dependencies = [ - "async-tungstenite 0.25.1", - "futures-util", - "http 0.2.12", - "jsonwebtoken", - "livekit-protocol", - "livekit-runtime", - "log", - "parking_lot", - "prost 0.12.6", - "reqwest 0.11.27", - "scopeguard", - "serde", - "serde_json", - "sha2", - "thiserror 1.0.69", - "tokio", - "tokio-tungstenite 0.20.1", - "url", -] - -[[package]] -name = "livekit-protocol" -version = "0.3.6" -source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" -dependencies = [ - "futures-util", - "livekit-runtime", - "parking_lot", - "pbjson", - "pbjson-types", - "prost 0.12.6", - "prost-types 0.12.6", - "serde", - "thiserror 1.0.69", - "tokio", -] - -[[package]] -name = "livekit-runtime" -version = "0.3.1" -source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" -dependencies = [ - "async-io 2.4.0", - "async-std", - "async-task", - "futures 0.3.31", -] - [[package]] name = "lmdb-master-sys" version = "0.2.4" @@ -7061,11 +6750,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.12.5" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" dependencies = [ - "hashbrown 0.15.1", + "hashbrown 0.14.5", ] [[package]] @@ -7077,7 +6766,7 @@ dependencies = [ "collections", "ctor", "env_logger 0.11.5", - "futures 0.3.31", + "futures 0.3.30", "gpui", "log", "lsp-types", @@ -7106,18 +6795,19 @@ dependencies = [ [[package]] name = "lz4" -version = "1.28.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d1febb2b4a79ddd1980eede06a8f7902197960aa0383ffcfdd62fe723036725" +checksum = "958b4caa893816eea05507c20cfe47574a43d9a697138a7872990bba8a0ece68" dependencies = [ + "libc", "lz4-sys", ] [[package]] name = "lz4-sys" -version = "1.11.1+lz4-1.10.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +checksum = "109de74d5d2353660401699a4174a4ff23fcc649caf553df71933c7fb45ad868" dependencies = [ "cc", "libc", @@ -7160,7 +6850,7 @@ dependencies = [ "anyhow", "assets", "env_logger 0.11.5", - "futures 0.3.31", + "futures 0.3.30", "gpui", "language", "languages", @@ -7248,7 +6938,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", - "rayon", ] [[package]] @@ -7302,9 +6991,8 @@ name = "media" version = "0.1.0" dependencies = [ "anyhow", - "bindgen", + "bindgen 0.70.1", "core-foundation 0.9.4", - "ctor", "foreign-types 0.5.0", "metal", "objc", @@ -7322,14 +7010,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.40", + "rustix 0.38.35", ] [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" dependencies = [ "libc", ] @@ -7397,6 +7085,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", + "simd-adler32", +] + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -7404,7 +7102,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", - "simd-adler32", ] [[package]] @@ -7465,7 +7162,7 @@ dependencies = [ "collections", "ctor", "env_logger 0.11.5", - "futures 0.3.31", + "futures 0.3.30", "gpui", "itertools 0.13.0", "language", @@ -7487,12 +7184,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" - [[package]] name = "naga" version = "22.1.0" @@ -7505,12 +7196,12 @@ dependencies = [ "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", - "indexmap 2.6.0", + "indexmap 2.4.0", "log", "rustc-hash 1.1.0", "spirv", "termcolor", - "thiserror 1.0.69", + "thiserror", "unicode-xid", ] @@ -7560,7 +7251,7 @@ dependencies = [ "jupyter-serde", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", "uuid", ] @@ -7575,7 +7266,7 @@ dependencies = [ "log", "ndk-sys", "num_enum", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -7623,7 +7314,7 @@ dependencies = [ "async-trait", "async-watch", "async_zip", - "futures 0.3.31", + "futures 0.3.30", "http_client", "log", "paths", @@ -7909,7 +7600,7 @@ version = "0.8.0-pre" source = "git+https://github.com/KillTheMule/nvim-rs?branch=master#69500bae73b8b3f02a05b7bee621a0d0e633da6c" dependencies = [ "async-trait", - "futures 0.3.31", + "futures 0.3.30", "log", "parity-tokio-ipc", "rmp", @@ -7930,13 +7621,13 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "crc32fast", - "hashbrown 0.15.1", - "indexmap 2.6.0", + "hashbrown 0.14.5", + "indexmap 2.4.0", "memchr", ] @@ -7968,7 +7659,7 @@ name = "ollama" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.31", + "futures 0.3.30", "http_client", "schemars", "serde", @@ -7977,9 +7668,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oo7" @@ -7989,7 +7680,7 @@ checksum = "8fc6ce4692fbfd044ce22ca07dcab1a30fa12432ca2aa5b1294eca50d3332a24" dependencies = [ "aes", "async-fs 2.1.2", - "async-io 2.4.0", + "async-io 2.3.4", "async-lock 3.4.0", "async-net 2.0.0", "blocking", @@ -7997,14 +7688,14 @@ dependencies = [ "cipher", "digest", "endi", - "futures-lite 2.5.0", + "futures-lite 2.3.0", "futures-util", "hkdf", "hmac", "md-5", "num", "num-bigint-dig", - "pbkdf2 0.12.2", + "pbkdf2", "rand 0.8.5", "serde", "sha2", @@ -8022,9 +7713,9 @@ checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "open" -version = "5.3.1" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ecd52f0b8d15c40ce4820aa251ed5de032e5d91fab27f7db2f40d42a8bdf69c" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" dependencies = [ "is-wsl", "libc", @@ -8036,7 +7727,7 @@ name = "open_ai" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.31", + "futures 0.3.30", "http_client", "schemars", "serde", @@ -8058,9 +7749,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -8090,18 +7781,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.4.0+3.4.0" +version = "300.3.1+3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" +checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -8287,7 +7978,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" dependencies = [ - "futures 0.3.31", + "futures 0.3.30", "libc", "log", "rand 0.7.3", @@ -8319,22 +8010,11 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.3", "smallvec", "windows-targets 0.52.6", ] -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "password-hash" version = "0.5.0" @@ -8385,55 +8065,6 @@ dependencies = [ "util", ] -[[package]] -name = "pbjson" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" -dependencies = [ - "base64 0.21.7", - "serde", -] - -[[package]] -name = "pbjson-build" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" -dependencies = [ - "heck 0.4.1", - "itertools 0.11.0", - "prost 0.12.6", - "prost-types 0.12.6", -] - -[[package]] -name = "pbjson-types" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" -dependencies = [ - "bytes 1.8.0", - "chrono", - "pbjson", - "pbjson-build", - "prost 0.12.6", - "prost-build 0.12.6", - "serde", -] - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest", - "hmac", - "password-hash 0.4.2", - "sha2", -] - [[package]] name = "pbkdf2" version = "0.12.2" @@ -8479,20 +8110,20 @@ dependencies = [ [[package]] name = "pest" -version = "2.7.14" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" dependencies = [ "memchr", - "thiserror 1.0.69", + "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.14" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" dependencies = [ "pest", "pest_generator", @@ -8500,9 +8131,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.14" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" dependencies = [ "pest", "pest_meta", @@ -8513,9 +8144,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.14" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" dependencies = [ "once_cell", "pest", @@ -8889,7 +8520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.6.0", + "indexmap 2.4.0", ] [[package]] @@ -8987,18 +8618,18 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", @@ -9007,9 +8638,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -9024,7 +8655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.2.0", + "fastrand 2.1.1", "futures-io", ] @@ -9061,9 +8692,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plist" @@ -9072,7 +8703,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ "base64 0.22.1", - "indexmap 2.6.0", + "indexmap 2.4.0", "quick-xml 0.32.0", "serde", "time", @@ -9080,9 +8711,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.7" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" dependencies = [ "num-traits", "plotters-backend", @@ -9093,30 +8724,30 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.7" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" [[package]] name = "plotters-svg" -version = "0.3.7" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" dependencies = [ "plotters-backend", ] [[package]] name = "png" -version = "0.17.14" +version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.7.4", ] [[package]] @@ -9137,15 +8768,15 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.4" +version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.40", + "rustix 0.38.35", "tracing", "windows-sys 0.59.0", ] @@ -9164,13 +8795,13 @@ checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" dependencies = [ "atomic", "crossbeam-queue", - "futures 0.3.31", + "futures 0.3.30", "log", "parking_lot", "pin-project", "pollster", "static_assertions", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -9237,9 +8868,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", "syn 2.0.87", @@ -9251,7 +8882,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.22.22", + "toml_edit 0.22.20", ] [[package]] @@ -9278,28 +8909,6 @@ dependencies = [ "version_check", ] -[[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 2.0.87", -] - [[package]] name = "proc-macro2" version = "1.0.89" @@ -9341,7 +8950,7 @@ dependencies = [ "env_logger 0.11.5", "fancy-regex 0.14.0", "fs", - "futures 0.3.31", + "futures 0.3.30", "fuzzy", "git", "git2", @@ -9425,7 +9034,7 @@ version = "0.1.0" dependencies = [ "anyhow", "editor", - "futures 0.3.31", + "futures 0.3.30", "fuzzy", "gpui", "language", @@ -9453,7 +9062,7 @@ dependencies = [ "memchr", "parking_lot", "protobuf", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -9462,18 +9071,8 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.8.0", - "prost-derive 0.9.0", -] - -[[package]] -name = "prost" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" -dependencies = [ - "bytes 1.8.0", - "prost-derive 0.12.6", + "bytes 1.7.2", + "prost-derive", ] [[package]] @@ -9482,41 +9081,20 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "heck 0.3.3", "itertools 0.10.5", "lazy_static", "log", - "multimap 0.8.3", + "multimap", "petgraph", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "regex", "tempfile", "which 4.4.2", ] -[[package]] -name = "prost-build" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" -dependencies = [ - "bytes 1.8.0", - "heck 0.5.0", - "itertools 0.12.1", - "log", - "multimap 0.10.0", - "once_cell", - "petgraph", - "prettyplease", - "prost 0.12.6", - "prost-types 0.12.6", - "regex", - "syn 2.0.87", - "tempfile", -] - [[package]] name = "prost-derive" version = "0.9.0" @@ -9530,36 +9108,14 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "prost-derive" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "prost-types" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.8.0", - "prost 0.9.0", -] - -[[package]] -name = "prost-types" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" -dependencies = [ - "prost 0.12.6", + "bytes 1.7.2", + "prost", ] [[package]] @@ -9568,8 +9124,8 @@ version = "0.1.0" dependencies = [ "anyhow", "collections", - "prost 0.9.0", - "prost-build 0.9.0", + "prost", + "prost-build", "serde", ] @@ -9581,9 +9137,9 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "psm" -version = "0.1.24" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" dependencies = [ "cc", ] @@ -9663,9 +9219,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.36.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" dependencies = [ "memchr", ] @@ -9690,49 +9246,45 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash 2.0.0", - "rustls 0.23.16", + "rustls 0.23.13", "socket2 0.5.7", - "thiserror 2.0.3", + "thiserror", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ - "bytes 1.8.0", - "getrandom 0.2.15", + "bytes 1.7.2", "rand 0.8.5", "ring", "rustc-hash 2.0.0", - "rustls 0.23.16", - "rustls-pki-types", + "rustls 0.23.13", "slab", - "thiserror 2.0.3", + "thiserror", "tinyvec", "tracing", - "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.7" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ - "cfg_aliases 0.2.1", "libc", "once_cell", "socket2 0.5.7", @@ -9862,23 +9414,22 @@ dependencies = [ "rand_chacha 0.3.1", "simd_helpers", "system-deps", - "thiserror 1.0.69", + "thiserror", "v_frame", "wasm-bindgen", ] [[package]] name = "ravif" -version = "0.11.11" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", - "rayon", "rgb", ] @@ -9922,9 +9473,9 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.22.5" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a04b892cb6f91951f144c33321843790c8574c825aafdb16d815fd7183b5229" +checksum = "8c141b9980e1150201b2a3a32879001c8f975fe313ec3df5471a9b5c79a880cd" dependencies = [ "bytemuck", "font-types", @@ -9938,7 +9489,7 @@ dependencies = [ "auto_update", "editor", "file_finder", - "futures 0.3.31", + "futures 0.3.30", "fuzzy", "gpui", "itertools 0.13.0", @@ -9975,9 +9526,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ "bitflags 2.6.0", ] @@ -9990,7 +9550,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -10015,14 +9575,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -10036,13 +9596,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax 0.8.4", ] [[package]] @@ -10059,9 +9619,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "release_channel" @@ -10079,13 +9639,13 @@ dependencies = [ "async-trait", "collections", "fs", - "futures 0.3.31", + "futures 0.3.30", "gpui", "itertools 0.13.0", "log", "parking_lot", "paths", - "prost 0.9.0", + "prost", "release_channel", "rpc", "serde", @@ -10093,7 +9653,7 @@ dependencies = [ "shlex", "smol", "tempfile", - "thiserror 1.0.69", + "thiserror", "util", "which 6.0.3", ] @@ -10113,7 +9673,7 @@ dependencies = [ "env_logger 0.11.5", "fork", "fs", - "futures 0.3.31", + "futures 0.3.30", "git", "git_hosting_providers", "gpui", @@ -10167,7 +9727,7 @@ dependencies = [ "editor", "env_logger 0.11.5", "feature_flags", - "futures 0.3.31", + "futures 0.3.30", "gpui", "http_client", "image", @@ -10207,7 +9767,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", - "bytes 1.8.0", + "bytes 1.7.2", "encoding_rs", "futures-core", "futures-util", @@ -10215,7 +9775,6 @@ dependencies = [ "http 0.2.12", "http-body 0.4.6", "hyper 0.14.31", - "hyper-rustls 0.24.2", "hyper-tls", "ipnet", "js-sys", @@ -10225,8 +9784,6 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.12", - "rustls-native-certs 0.6.3", "rustls-pemfile 1.0.4", "serde", "serde_json", @@ -10235,7 +9792,6 @@ dependencies = [ "system-configuration 0.5.1", "tokio", "tokio-native-tls", - "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", @@ -10250,7 +9806,7 @@ version = "0.12.8" source = "git+https://github.com/zed-industries/reqwest.git?rev=fd110f6998da16bbca97b6dddda9be7827c50e29#fd110f6998da16bbca97b6dddda9be7827c50e29" dependencies = [ "base64 0.22.1", - "bytes 1.8.0", + "bytes 1.7.2", "encoding_rs", "futures-core", "futures-util", @@ -10258,7 +9814,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.4.1", "hyper-rustls 0.27.3", "hyper-util", "ipnet", @@ -10269,9 +9825,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.16", + "rustls 0.23.13", "rustls-native-certs 0.8.0", - "rustls-pemfile 2.2.0", + "rustls-pemfile 2.1.3", "rustls-pki-types", "serde", "serde_json", @@ -10296,8 +9852,8 @@ name = "reqwest_client" version = "0.1.0" dependencies = [ "anyhow", - "bytes 1.8.0", - "futures 0.3.31", + "bytes 1.7.2", + "futures 0.3.30", "gpui", "http_client", "log", @@ -10334,9 +9890,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "09cd5a1e95672f201913966f39baf355b53b5d92833431847295ae0346a5b939" dependencies = [ "bytemuck", ] @@ -10345,7 +9901,7 @@ dependencies = [ name = "rich_text" version = "0.1.0" dependencies = [ - "futures 0.3.31", + "futures 0.3.30", "gpui", "language", "linkify", @@ -10378,7 +9934,7 @@ checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", - "bytes 1.8.0", + "bytes 1.7.2", "hashbrown 0.12.3", "ptr_meta", "rend", @@ -10428,7 +9984,7 @@ checksum = "6006a627c1a38d37f3d3a85c6575418cfe34a5392d60a686d0071e1c8d427acb" dependencies = [ "cpal", "hound", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -10460,12 +10016,12 @@ name = "rpc" version = "0.1.0" dependencies = [ "anyhow", - "async-tungstenite 0.28.0", + "async-tungstenite", "base64 0.22.1", "chrono", "collections", "env_logger 0.11.5", - "futures 0.3.31", + "futures 0.3.30", "gpui", "parking_lot", "proto", @@ -10510,11 +10066,11 @@ dependencies = [ "async-dispatcher", "async-std", "base64 0.22.1", - "bytes 1.8.0", + "bytes 1.7.2", "chrono", "data-encoding", "dirs 5.0.1", - "futures 0.3.31", + "futures 0.3.30", "glob", "jupyter-serde", "rand 0.8.5", @@ -10570,7 +10126,7 @@ checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", "borsh", - "bytes 1.8.0", + "bytes 1.7.2", "num-traits", "rand 0.8.5", "rkyv", @@ -10621,9 +10177,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags 2.6.0", "errno 0.3.9", @@ -10642,7 +10198,7 @@ checksum = "a25c3aad9fc1424eb82c88087789a7d938e1829724f3e4043163baf0d13cfc12" dependencies = [ "errno 0.3.9", "libc", - "rustix 0.38.40", + "rustix 0.38.35", ] [[package]] @@ -10659,9 +10215,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.16" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "once_cell", "ring", @@ -10690,7 +10246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", - "rustls-pemfile 2.2.0", + "rustls-pemfile 2.1.3", "rustls-pki-types", "schannel", "security-framework", @@ -10707,21 +10263,19 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.2.0" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ + "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" -dependencies = [ - "web-time", -] +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" @@ -10746,9 +10300,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rustybuzz" @@ -10793,11 +10347,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -10837,20 +10391,14 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "scratch" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3cf7c11c38cb994f3d40e8a8cde3bbd1f72a435e4c49e85d6553d8312306152" - [[package]] name = "scrypt" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" dependencies = [ - "password-hash 0.5.0", - "pbkdf2 0.12.2", + "password-hash", + "pbkdf2", "salsa20", "sha2", ] @@ -10867,12 +10415,12 @@ dependencies = [ [[package]] name = "sea-bae" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" +checksum = "3bd3534a9978d0aa7edd2808dc1f8f31c4d0ecd31ddf71d997b3c98e9f3c9114" dependencies = [ "heck 0.4.1", - "proc-macro-error2", + "proc-macro-error", "proc-macro2", "quote", "syn 2.0.87", @@ -10888,7 +10436,7 @@ dependencies = [ "async-trait", "bigdecimal", "chrono", - "futures 0.3.31", + "futures 0.3.30", "log", "ouroboros", "rust_decimal", @@ -10899,7 +10447,7 @@ dependencies = [ "serde_json", "sqlx", "strum 0.26.3", - "thiserror 1.0.69", + "thiserror", "time", "tracing", "url", @@ -10908,9 +10456,9 @@ dependencies = [ [[package]] name = "sea-orm-macros" -version = "1.1.1" +version = "1.1.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a239e3bb1b566ad4ec2654d0d193d6ceddfd733487edc9c21a64d214c773910" +checksum = "07aadcb2ee9fad78a3bf74f6430ba94865ab4d8ad237f978e99dafa97ee0df57" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -10922,12 +10470,13 @@ dependencies = [ [[package]] name = "sea-query" -version = "0.32.0" +version = "0.32.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff504d13b5e4b52fffcf2fb203d0352a5722fa5151696db768933e41e1e591bb" +checksum = "6fba498acd58ce434669f273505cd07737065472eb541c3f813c7f4ce33993f5" dependencies = [ "bigdecimal", "chrono", + "educe", "inherent", "ordered-float 3.9.2", "rust_decimal", @@ -10938,9 +10487,9 @@ dependencies = [ [[package]] name = "sea-query-binder" -version = "0.7.0" +version = "0.7.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608" +checksum = "edc3296903e60ddc7c9f4601cd6ef31a4b1584bf22480587e00b9ef743071b57" dependencies = [ "bigdecimal", "chrono", @@ -10980,7 +10529,7 @@ dependencies = [ "client", "collections", "editor", - "futures 0.3.31", + "futures 0.3.30", "gpui", "language", "menu", @@ -11025,9 +10574,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -11052,7 +10601,7 @@ dependencies = [ "env_logger 0.11.5", "feature_flags", "fs", - "futures 0.3.31", + "futures 0.3.30", "futures-batch", "gpui", "heed", @@ -11099,18 +10648,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", @@ -11154,7 +10703,7 @@ version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.4.0", "itoa", "memchr", "ryu", @@ -11163,13 +10712,12 @@ dependencies = [ [[package]] name = "serde_json_lenient" -version = "0.2.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf0c7e21364d0e199dd2f6c339ca18d6fca75b69458a247e8b27ff1c92f5b86" +checksum = "a5d0bae483150302560d7cb52e7932f39b69a6fbdd099e48d33ef060a8c9c078" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.4.0", "itoa", - "memchr", "ryu", "serde", ] @@ -11192,7 +10740,7 @@ checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" dependencies = [ "percent-encoding", "serde", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -11203,7 +10751,7 @@ checksum = "8cac3f1e2ca2fe333923a1ae72caca910b98ed0630bb35ef6f8c8517d6e81afa" dependencies = [ "percent-encoding", "serde", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -11219,9 +10767,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -11257,7 +10805,7 @@ dependencies = [ "collections", "ec4rs", "fs", - "futures 0.3.31", + "futures 0.3.30", "gpui", "indoc", "log", @@ -11414,9 +10962,9 @@ dependencies = [ [[package]] name = "simdutf8" -version = "0.1.5" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" [[package]] name = "similar" @@ -11432,7 +10980,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", "num-traits", - "thiserror 1.0.69", + "thiserror", "time", ] @@ -11470,9 +11018,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "skrifa" -version = "0.22.3" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe" +checksum = "abea4738067b1e628c6ce28b2c216c19e9ea95715cdb332680e821c3bec2ef23" dependencies = [ "bytemuck", "read-fonts", @@ -11570,7 +11118,7 @@ dependencies = [ "anyhow", "collections", "fs", - "futures 0.3.31", + "futures 0.3.30", "gpui", "parking_lot", "paths", @@ -11616,9 +11164,9 @@ dependencies = [ [[package]] name = "spdx" -version = "0.10.7" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae30cc7bfe3656d60ee99bf6836f472b0c53dddcbf335e253329abb16e535a2" +checksum = "47317bbaf63785b53861e1ae2d11b80d6b624211d42cb20efcd210ee6f8a14bc" dependencies = [ "smallvec", ] @@ -11673,7 +11221,7 @@ version = "0.1.0" dependencies = [ "anyhow", "collections", - "futures 0.3.31", + "futures 0.3.30", "indoc", "libsqlite3-sys", "parking_lot", @@ -11725,7 +11273,7 @@ dependencies = [ "atoi", "bigdecimal", "byteorder", - "bytes 1.8.0", + "bytes 1.7.2", "chrono", "crc", "crossbeam-queue", @@ -11739,7 +11287,7 @@ dependencies = [ "hashbrown 0.14.5", "hashlink 0.9.1", "hex", - "indexmap 2.6.0", + "indexmap 2.4.0", "log", "memchr", "once_cell", @@ -11753,7 +11301,7 @@ dependencies = [ "sha2", "smallvec", "sqlformat", - "thiserror 1.0.69", + "thiserror", "time", "tokio", "tokio-stream", @@ -11813,7 +11361,7 @@ dependencies = [ "bigdecimal", "bitflags 2.6.0", "byteorder", - "bytes 1.8.0", + "bytes 1.7.2", "chrono", "crc", "digest", @@ -11842,7 +11390,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 1.0.69", + "thiserror", "time", "tracing", "uuid", @@ -11886,7 +11434,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 1.0.69", + "thiserror", "time", "tracing", "uuid", @@ -12076,7 +11624,7 @@ dependencies = [ "collections", "editor", "env_logger 0.11.5", - "futures 0.3.31", + "futures 0.3.30", "gpui", "http_client", "language", @@ -12101,7 +11649,7 @@ name = "supermaven_api" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.31", + "futures 0.3.30", "http_client", "paths", "serde", @@ -12111,15 +11659,15 @@ dependencies = [ [[package]] name = "sval" -version = "2.13.2" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6dc0f9830c49db20e73273ffae9b5240f63c42e515af1da1fceefb69fceafd8" +checksum = "53eb957fbc79a55306d5d25d87daf3627bc3800681491cda0709eef36c748bfe" [[package]] name = "sval_buffer" -version = "2.13.2" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "429922f7ad43c0ef8fd7309e14d750e38899e32eb7e8da656ea169dd28ee212f" +checksum = "96e860aef60e9cbf37888d4953a13445abf523c534640d1f6174d310917c410d" dependencies = [ "sval", "sval_ref", @@ -12127,18 +11675,18 @@ dependencies = [ [[package]] name = "sval_dynamic" -version = "2.13.2" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f16ff5d839396c11a30019b659b0976348f3803db0626f736764c473b50ff4" +checksum = "ea3f2b07929a1127d204ed7cb3905049381708245727680e9139dac317ed556f" dependencies = [ "sval", ] [[package]] name = "sval_fmt" -version = "2.13.2" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c01c27a80b6151b0557f9ccbe89c11db571dc5f68113690c1e028d7e974bae94" +checksum = "c4e188677497de274a1367c4bda15bd2296de4070d91729aac8f0a09c1abf64d" dependencies = [ "itoa", "ryu", @@ -12147,9 +11695,9 @@ dependencies = [ [[package]] name = "sval_json" -version = "2.13.2" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0deef63c70da622b2a8069d8600cf4b05396459e665862e7bdb290fd6cf3f155" +checksum = "32f456c07dae652744781f2245d5e3b78e6a9ebad70790ac11eb15dbdbce5282" dependencies = [ "itoa", "ryu", @@ -12158,9 +11706,9 @@ dependencies = [ [[package]] name = "sval_nested" -version = "2.13.2" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a39ce5976ae1feb814c35d290cf7cf8cd4f045782fe1548d6bc32e21f6156e9f" +checksum = "886feb24709f0476baaebbf9ac10671a50163caa7e439d7a7beb7f6d81d0a6fb" dependencies = [ "sval", "sval_buffer", @@ -12169,18 +11717,18 @@ dependencies = [ [[package]] name = "sval_ref" -version = "2.13.2" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7c6ee3751795a728bc9316a092023529ffea1783499afbc5c66f5fabebb1fa" +checksum = "be2e7fc517d778f44f8cb64140afa36010999565528d48985f55e64d45f369ce" dependencies = [ "sval", ] [[package]] name = "sval_serde" -version = "2.13.2" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5572d0321b68109a343634e3a5d576bf131b82180c6c442dee06349dfc652a" +checksum = "79bf66549a997ff35cd2114a27ac4b0c2843280f2cfa84b240d169ecaa0add46" dependencies = [ "serde", "sval", @@ -12189,9 +11737,9 @@ dependencies = [ [[package]] name = "svg_fmt" -version = "0.4.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" +checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" [[package]] name = "svgtypes" @@ -12205,9 +11753,9 @@ dependencies = [ [[package]] name = "swash" -version = "0.1.19" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2" +checksum = "93cdc334a50fcc2aa3f04761af3b28196280a6aaadb1ef11215c478ae32615ac" dependencies = [ "skrifa", "yazi", @@ -12236,6 +11784,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -12260,17 +11820,6 @@ dependencies = [ "crossbeam-queue", ] -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "sys-locale" version = "0.3.2" @@ -12291,7 +11840,7 @@ dependencies = [ "memchr", "ntapi", "rayon", - "windows 0.57.0", + "windows 0.54.0", ] [[package]] @@ -12360,7 +11909,7 @@ dependencies = [ "cap-std", "fd-lock", "io-lifetimes 2.0.3", - "rustix 0.38.40", + "rustix 0.38.35", "windows-sys 0.52.0", "winx", ] @@ -12425,7 +11974,7 @@ version = "0.1.0" dependencies = [ "anyhow", "collections", - "futures 0.3.31", + "futures 0.3.30", "gpui", "hex", "parking_lot", @@ -12472,14 +12021,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", - "fastrand 2.2.0", + "fastrand 2.1.1", "once_cell", - "rustix 0.38.40", + "rustix 0.38.35", "windows-sys 0.59.0", ] @@ -12511,7 +12060,7 @@ dependencies = [ "anyhow", "collections", "dirs 4.0.0", - "futures 0.3.31", + "futures 0.3.30", "gpui", "libc", "rand 0.8.5", @@ -12525,7 +12074,7 @@ dependencies = [ "sysinfo", "task", "theme", - "thiserror 1.0.69", + "thiserror", "util", "windows 0.58.0", ] @@ -12536,7 +12085,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ - "rustix 0.38.40", + "rustix 0.38.35", "windows-sys 0.59.0", ] @@ -12550,7 +12099,7 @@ dependencies = [ "db", "dirs 4.0.0", "editor", - "futures 0.3.31", + "futures 0.3.30", "gpui", "itertools 0.13.0", "language", @@ -12602,7 +12151,7 @@ dependencies = [ "collections", "derive_more", "fs", - "futures 0.3.31", + "futures 0.3.30", "gpui", "indexmap 1.9.3", "log", @@ -12666,16 +12215,7 @@ 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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" -dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl", ] [[package]] @@ -12689,17 +12229,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "thiserror-impl" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "thread_local" version = "1.1.8" @@ -12827,16 +12356,6 @@ dependencies = [ "url", ] -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinytemplate" version = "1.2.1" @@ -12900,12 +12419,12 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.1" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", - "bytes 1.8.0", + "bytes 1.7.2", "libc", "mio 1.0.2", "parking_lot", @@ -12964,7 +12483,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.16", + "rustls 0.23.13", "rustls-pki-types", "tokio", ] @@ -12978,15 +12497,15 @@ dependencies = [ "either", "futures-io", "futures-util", - "thiserror 1.0.69", + "thiserror", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -13001,10 +12520,7 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "rustls 0.21.12", - "rustls-native-certs 0.6.3", "tokio", - "tokio-rustls 0.24.1", "tungstenite 0.20.1", ] @@ -13026,7 +12542,7 @@ version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "futures-core", "futures-io", "futures-sink", @@ -13064,7 +12580,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.22", + "toml_edit 0.22.20", ] [[package]] @@ -13082,7 +12598,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.4.0", "serde", "serde_spanned", "toml_datetime", @@ -13091,15 +12607,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.4.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.20", + "winnow 0.6.18", ] [[package]] @@ -13146,7 +12662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ "bitflags 1.3.2", - "bytes 1.8.0", + "bytes 1.7.2", "futures-core", "futures-util", "http 0.2.12", @@ -13164,7 +12680,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "bitflags 2.6.0", - "bytes 1.8.0", + "bytes 1.7.2", "futures-core", "futures-util", "http 0.2.12", @@ -13265,22 +12781,22 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.23.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0203df02a3b6dd63575cc1d6e609edc2181c9a11867a271b25cfd2abff3ec5ca" +checksum = "20f4cd3642c47a85052a887d86704f4eac272969f61b686bdd3f772122aabaff" dependencies = [ "cc", "regex", - "regex-syntax 0.8.5", + "regex-syntax 0.8.4", "tree-sitter-language", "wasmtime-c-api-impl", ] [[package]] name = "tree-sitter-bash" -version = "0.23.3" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "329a4d48623ac337d42b1df84e81a1c9dbb2946907c102ca72db158c1964a52e" +checksum = "3aa5e1c6bd02c0053f3f68edcf5d8866b38a8640584279e30fca88149ce14dda" dependencies = [ "cc", "tree-sitter-language", @@ -13298,9 +12814,9 @@ dependencies = [ [[package]] name = "tree-sitter-cpp" -version = "0.23.4" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2196ea9d47b4ab4a31b9297eaa5a5d19a0b121dceb9f118f6790ad0ab94743" +checksum = "1d67e862242878d6ee50e1e5814f267ee3eea0168aea2cdbd700ccfb4c74b6d3" dependencies = [ "cc", "tree-sitter-language", @@ -13308,9 +12824,9 @@ dependencies = [ [[package]] name = "tree-sitter-css" -version = "0.23.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25435a275adb3226b6fddab891bbc50d1a500774a44ceb97022a39666ccda75d" +checksum = "8d0018d6b1692a806f9cddaa1e5616951fd58840c39a0b21401b55ab3df12292" dependencies = [ "cc", "tree-sitter-language", @@ -13338,9 +12854,9 @@ dependencies = [ [[package]] name = "tree-sitter-embedded-template" -version = "0.23.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790063ef14e5b67556abc0b3be0ed863fb41d65ee791cf8c0b20eb42a1fa46af" +checksum = "9644d7586ebe850c84037ee2f4804dda4a9348eef053be6b1e0d7712342a2495" dependencies = [ "cc", "tree-sitter-language", @@ -13348,9 +12864,9 @@ dependencies = [ [[package]] name = "tree-sitter-go" -version = "0.23.3" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4ee804a89f5c0e606b0b20579c86afc7cd0174aebd45c33b6b9c6237bcd97d" +checksum = "caf57626e4c9b6d6efaf8a8d5ee1241c5f178ae7bfdf693713ae6a774f01424e" dependencies = [ "cc", "tree-sitter-language", @@ -13395,9 +12911,9 @@ dependencies = [ [[package]] name = "tree-sitter-jsdoc" -version = "0.23.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3862dfcb1038fc5e7812d7df14190afdeb7e1415288fd5f51f58395f8cb0faf" +checksum = "f8c4049eb0ad690e34e5f63640f75ce12a2ff8ba18344d0a13926805b139c0c8" dependencies = [ "cc", "tree-sitter-language", @@ -13415,9 +12931,9 @@ dependencies = [ [[package]] name = "tree-sitter-language" -version = "0.1.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ddffe35a0e5eeeadf13ff7350af564c6e73993a24db62caee1822b185c2600" +checksum = "2545046bd1473dac6c626659cc2567c6c0ff302fc8b84a56c4243378276f7f57" [[package]] name = "tree-sitter-md" @@ -13430,9 +12946,9 @@ dependencies = [ [[package]] name = "tree-sitter-python" -version = "0.23.4" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2416de7eea3f2e1bd53c250f2d3f3394fc77f78497680f37f4b87918b8d752e3" +checksum = "65661b1a3e24139e2e54207e47d910ab07e28790d78efc7d5dc3a11ce2a110eb" dependencies = [ "cc", "tree-sitter-language", @@ -13450,9 +12966,9 @@ dependencies = [ [[package]] name = "tree-sitter-ruby" -version = "0.23.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0484ea4ef6bb9c575b4fdabde7e31340a8d2dbc7d52b321ac83da703249f95" +checksum = "6ec5ee842e27791e0adffa0b2a177614de51d2a26e5c7e84d014ed7f097e5ed0" dependencies = [ "cc", "tree-sitter-language", @@ -13460,9 +12976,9 @@ dependencies = [ [[package]] name = "tree-sitter-rust" -version = "0.23.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "137ff3de3cc8a98302d048963459ead91135d4a1b423f09d25028b847ec3d3e3" +checksum = "cffbbcb780348fbae8395742ae5b34c1fd794e4085d43aac9f259387f9a84dc8" dependencies = [ "cc", "tree-sitter-language", @@ -13470,9 +12986,9 @@ dependencies = [ [[package]] name = "tree-sitter-typescript" -version = "0.23.2" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff" +checksum = "aecf1585ae2a9dddc2b1d4c0e2140b2ec9876e2a25fd79de47fcf7dae0384685" dependencies = [ "cc", "tree-sitter-language", @@ -13506,15 +13022,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ "byteorder", - "bytes 1.8.0", + "bytes 1.7.2", "data-encoding", "http 0.2.12", "httparse", "log", "rand 0.8.5", - "rustls 0.21.12", "sha1", - "thiserror 1.0.69", + "thiserror", "url", "utf-8", ] @@ -13526,15 +13041,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" dependencies = [ "byteorder", - "bytes 1.8.0", + "bytes 1.7.2", "data-encoding", "http 1.1.0", "httparse", "log", - "native-tls", "rand 0.8.5", "sha1", - "thiserror 1.0.69", + "thiserror", "url", "utf-8", ] @@ -13546,14 +13060,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" dependencies = [ "byteorder", - "bytes 1.8.0", + "bytes 1.7.2", "data-encoding", "http 1.1.0", "httparse", "log", "rand 0.8.5", "sha1", - "thiserror 1.0.69", + "thiserror", "utf-8", ] @@ -13571,9 +13085,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.7" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "uds_windows" @@ -13627,15 +13141,18 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] [[package]] name = "unicode-bidi" -version = "0.3.17" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-bidi-mirroring" @@ -13651,9 +13168,9 @@ checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-linebreak" @@ -13663,18 +13180,18 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.3" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] name = "unicode-script" @@ -13684,21 +13201,21 @@ checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.14" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" -version = "0.2.6" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "unicode_categories" @@ -13720,9 +13237,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.3" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -13764,18 +13281,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -13790,7 +13295,7 @@ dependencies = [ "async-fs 1.6.0", "collections", "dirs 4.0.0", - "futures 0.3.31", + "futures 0.3.30", "futures-lite 1.13.0", "git2", "globset", @@ -13808,9 +13313,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.11.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom 0.2.15", "serde", @@ -13836,9 +13341,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.10.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" +checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" dependencies = [ "value-bag-serde1", "value-bag-sval2", @@ -13846,9 +13351,9 @@ dependencies = [ [[package]] name = "value-bag-serde1" -version = "1.10.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb773bd36fd59c7ca6e336c94454d9c66386416734817927ac93d81cb3c5b0b" +checksum = "ccacf50c5cb077a9abb723c5bcb5e0754c1a433f1e1de89edc328e2760b6328b" dependencies = [ "erased-serde", "serde", @@ -13857,9 +13362,9 @@ dependencies = [ [[package]] name = "value-bag-sval2" -version = "1.10.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a916a702cac43a88694c97657d449775667bcd14b70419441d05b7fea4a83a" +checksum = "1785bae486022dfb9703915d42287dcb284c1ee37bd1080eeba78cc04721285b" dependencies = [ "sval", "sval_buffer", @@ -13914,7 +13419,7 @@ dependencies = [ "command_palette", "command_palette_hooks", "editor", - "futures 0.3.31", + "futures 0.3.30", "gpui", "indoc", "itertools 0.13.0", @@ -14029,7 +13534,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" dependencies = [ - "bytes 1.8.0", + "bytes 1.7.2", "futures-channel", "futures-util", "headers", @@ -14071,9 +13576,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", "once_cell", @@ -14082,9 +13587,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", @@ -14097,9 +13602,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -14109,9 +13614,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -14119,9 +13624,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", @@ -14132,9 +13637,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-encoder" @@ -14161,7 +13666,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2" dependencies = [ "anyhow", - "indexmap 2.6.0", + "indexmap 2.4.0", "serde", "serde_derive", "serde_json", @@ -14172,9 +13677,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" dependencies = [ "futures-util", "js-sys", @@ -14190,7 +13695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" dependencies = [ "bitflags 2.6.0", - "indexmap 2.6.0", + "indexmap 2.4.0", "semver", ] @@ -14203,7 +13708,7 @@ dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", "hashbrown 0.14.5", - "indexmap 2.6.0", + "indexmap 2.4.0", "semver", "serde", ] @@ -14233,7 +13738,7 @@ dependencies = [ "cfg-if", "encoding_rs", "hashbrown 0.14.5", - "indexmap 2.6.0", + "indexmap 2.4.0", "libc", "libm", "log", @@ -14244,7 +13749,7 @@ dependencies = [ "paste", "postcard", "psm", - "rustix 0.38.40", + "rustix 0.38.35", "semver", "serde", "serde_derive", @@ -14276,9 +13781,9 @@ dependencies = [ [[package]] name = "wasmtime-c-api-impl" -version = "24.0.2" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e038dd412700174019867608617127e7cc4f113f764dd10e7488dbf5f47b191" +checksum = "765e302e7d9125e614aaeec3ad6b6083605393004eca00214106a4ff6b47fc58" dependencies = [ "anyhow", "log", @@ -14290,9 +13795,9 @@ dependencies = [ [[package]] name = "wasmtime-c-api-macros" -version = "24.0.2" +version = "24.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde0ca2263811d980ab676bcb2a190c990737f58969a908976101ad208149a17" +checksum = "2d09d02eaa84aa2de5babee7b0296557ad6e4903bb10aa8d135e393e753a43d6" dependencies = [ "proc-macro2", "quote", @@ -14337,7 +13842,7 @@ dependencies = [ "log", "object", "target-lexicon", - "thiserror 1.0.69", + "thiserror", "wasmparser 0.215.0", "wasmtime-environ", "wasmtime-versioned-export-macros", @@ -14354,7 +13859,7 @@ dependencies = [ "cranelift-bitset", "cranelift-entity", "gimli 0.29.0", - "indexmap 2.6.0", + "indexmap 2.4.0", "log", "object", "postcard", @@ -14379,7 +13884,7 @@ dependencies = [ "anyhow", "cc", "cfg-if", - "rustix 0.38.40", + "rustix 0.38.35", "wasmtime-asm-macros", "wasmtime-versioned-export-macros", "windows-sys 0.52.0", @@ -14430,27 +13935,27 @@ dependencies = [ [[package]] name = "wasmtime-wasi" -version = "24.0.2" +version = "24.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88f94e393084426f5055d57ce7ae6346ae623783ee6792f411282d6b9e1e5c3" +checksum = "fda03f5bfd5c4cc09f75c7e44846663f25f2c48a2d688fbfb5c7a33af6cf34f5" dependencies = [ "anyhow", "async-trait", "bitflags 2.6.0", - "bytes 1.8.0", + "bytes 1.7.2", "cap-fs-ext", "cap-net-ext", "cap-rand", "cap-std", "cap-time-ext", "fs-set-times", - "futures 0.3.31", + "futures 0.3.30", "io-extras", "io-lifetimes 2.0.3", "once_cell", - "rustix 0.38.40", + "rustix 0.38.35", "system-interface", - "thiserror 1.0.69", + "thiserror", "tokio", "tracing", "url", @@ -14484,7 +13989,7 @@ checksum = "c58b085b2d330e5057dddd31f3ca527569b90fcdd35f6d373420c304927a5190" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.6.0", + "indexmap 2.4.0", "wit-parser 0.215.0", ] @@ -14499,13 +14004,13 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.7" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" +checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.40", + "rustix 0.38.35", "scoped-tls", "smallvec", "wayland-sys", @@ -14513,23 +14018,23 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.7" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" +checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" dependencies = [ "bitflags 2.6.0", - "rustix 0.38.40", + "rustix 0.38.35", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-cursor" -version = "0.31.7" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" +checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95" dependencies = [ - "rustix 0.38.40", + "rustix 0.38.35", "wayland-client", "xcursor", ] @@ -14561,20 +14066,20 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.5" +version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" +checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6" dependencies = [ "proc-macro2", - "quick-xml 0.36.2", + "quick-xml 0.34.0", "quote", ] [[package]] name = "wayland-sys" -version = "0.31.5" +version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" +checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" dependencies = [ "dlib", "log", @@ -14584,19 +14089,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" -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" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -14627,32 +14122,6 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" -[[package]] -name = "webrtc-sys" -version = "0.3.5" -source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" -dependencies = [ - "cc", - "cxx", - "cxx-build", - "glob", - "log", - "webrtc-sys-build", -] - -[[package]] -name = "webrtc-sys-build" -version = "0.3.5" -source = "git+https://github.com/zed-industries/rust-sdks?rev=4262308983646ab5b0e0802c3d8bc52154f99aab#4262308983646ab5b0e0802c3d8bc52154f99aab" -dependencies = [ - "fs2", - "regex", - "reqwest 0.11.27", - "scratch", - "semver", - "zip", -] - [[package]] name = "weezl" version = "0.1.8" @@ -14694,7 +14163,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.40", + "rustix 0.38.35", ] [[package]] @@ -14705,30 +14174,30 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", - "rustix 0.38.40", + "rustix 0.38.35", "winsafe", ] [[package]] name = "whoami" -version = "1.5.2" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ - "redox_syscall 0.5.7", + "redox_syscall 0.4.1", "wasite", ] [[package]] name = "wiggle" -version = "24.0.2" +version = "24.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c72a4c92952216582f55eab27819a1fe8d3c54b292b7b8e5f849b23bfed96e78" +checksum = "2d3b31bd2b4d2d82a4b747b8dbc45f566214214a4ffdc5690429a73bc221dc8a" dependencies = [ "anyhow", "async-trait", "bitflags 2.6.0", - "thiserror 1.0.69", + "thiserror", "tracing", "wasmtime", "wiggle-macro", @@ -14736,9 +14205,9 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "24.0.2" +version = "24.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb744fb938a9fc38207838829b4a43831c1de499e3526eaea71deeff4d9cbb83" +checksum = "e2c6136b195fc12067aa9d4e7a5baf118729394df7bc7cbf8c63119bc9f2a7cd" dependencies = [ "anyhow", "heck 0.4.1", @@ -14751,9 +14220,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "24.0.2" +version = "24.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cef395fff17bf8f9c1dee6c0e12801a3ba24928139af0ecb5ccb82ff87bf9d2" +checksum = "8a41eaceee468da976ac43b85c4eb82e482f828d5e8e56f49f90dfac2d9bc3b4" dependencies = [ "proc-macro2", "quote", @@ -14819,16 +14288,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[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" @@ -14858,42 +14317,19 @@ dependencies = [ "windows-targets 0.52.6", ] -[[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-implement", + "windows-interface", "windows-result 0.2.0", "windows-strings", "windows-targets 0.52.6", ] -[[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 2.0.87", -] - [[package]] name = "windows-implement" version = "0.58.0" @@ -14905,17 +14341,6 @@ dependencies = [ "syn 2.0.87", ] -[[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 2.0.87", -] - [[package]] name = "windows-interface" version = "0.58.0" @@ -15191,9 +14616,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -15288,7 +14713,7 @@ checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.6.0", + "indexmap 2.4.0", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -15316,7 +14741,7 @@ checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825" dependencies = [ "anyhow", "bitflags 2.6.0", - "indexmap 2.6.0", + "indexmap 2.4.0", "log", "serde", "serde_derive", @@ -15335,7 +14760,7 @@ checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" dependencies = [ "anyhow", "id-arena", - "indexmap 2.6.0", + "indexmap 2.4.0", "log", "semver", "serde", @@ -15353,7 +14778,7 @@ checksum = "935a97eaffd57c3b413aa510f8f0b550a4a9fe7d59e79cd8b89a83dcb860321f" dependencies = [ "anyhow", "id-arena", - "indexmap 2.6.0", + "indexmap 2.4.0", "log", "semver", "serde", @@ -15371,7 +14796,7 @@ checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" dependencies = [ "anyhow", "log", - "thiserror 1.0.69", + "thiserror", "wast", ] @@ -15391,7 +14816,7 @@ dependencies = [ "derive_more", "env_logger 0.11.5", "fs", - "futures 0.3.31", + "futures 0.3.30", "git", "gpui", "http_client", @@ -15428,7 +14853,7 @@ dependencies = [ "collections", "env_logger 0.11.5", "fs", - "futures 0.3.31", + "futures 0.3.30", "fuzzy", "git", "git2", @@ -15455,18 +14880,6 @@ dependencies = [ "util", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - [[package]] name = "wyz" version = "0.5.1" @@ -15478,9 +14891,9 @@ dependencies = [ [[package]] name = "x11-clipboard" -version = "0.9.3" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" +checksum = "b98785a09322d7446e28a13203d2cae1059a0dd3dfb32cb06d0a225f023d8286" dependencies = [ "libc", "x11rb", @@ -15495,7 +14908,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "rustix 0.38.40", + "rustix 0.38.35", "x11rb-protocol", ] @@ -15643,30 +15056,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "yoke" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", - "synstructure", -] - [[package]] name = "zbus" version = "4.4.0" @@ -15676,9 +15065,9 @@ dependencies = [ "async-broadcast", "async-executor", "async-fs 2.1.2", - "async-io 2.4.0", + "async-io 2.3.4", "async-lock 3.4.0", - "async-process 2.3.0", + "async-process 2.2.4", "async-recursion 1.1.1", "async-task", "async-trait", @@ -15767,7 +15156,7 @@ dependencies = [ "file_finder", "file_icons", "fs", - "futures 0.3.31", + "futures 0.3.30", "git", "git_hosting_providers", "go_to_line", @@ -16069,27 +15458,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "zerofrom" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", - "synstructure", -] - [[package]] name = "zeroize" version = "1.8.1" @@ -16112,15 +15480,15 @@ dependencies = [ [[package]] name = "zeromq" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a4528179201f6eecf211961a7d3276faa61554c82651ecc66387f68fc3004bd" +checksum = "fb0560d00172817b7f7c2265060783519c475702ae290b154115ca75e976d4d0" dependencies = [ "async-dispatcher", "async-std", "async-trait", "asynchronous-codec", - "bytes 1.8.0", + "bytes 1.7.2", "crossbeam-queue", "dashmap 5.5.3", "futures-channel", @@ -16133,52 +15501,10 @@ dependencies = [ "parking_lot", "rand 0.8.5", "regex", - "thiserror 1.0.69", + "thiserror", "uuid", ] -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "zip" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" -dependencies = [ - "aes", - "byteorder", - "bzip2", - "constant_time_eq 0.1.5", - "crc32fast", - "crossbeam-utils", - "flate2", - "hmac", - "pbkdf2 0.11.0", - "sha1", - "time", - "zstd", -] - [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/Cargo.toml b/Cargo.toml index 9d86bc1b20..c6d182fd58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -363,7 +363,6 @@ heed = { version = "0.20.1", features = ["read-txn-no-tls"] } hex = "0.4.3" html5ever = "0.27.0" hyper = "0.14" -http = "1.1" ignore = "0.4.22" image = "0.25.1" indexmap = { version = "1.6.2", features = ["serde"] } @@ -372,7 +371,6 @@ itertools = "0.13.0" jsonwebtoken = "9.3" libc = "0.2" linkify = "0.10.0" -livekit = { git = "https://github.com/zed-industries/rust-sdks", rev="4262308983646ab5b0e0802c3d8bc52154f99aab", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false } log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] } markup5ever_rcdom = "0.3.0" nanoid = "0.4" @@ -551,10 +549,6 @@ features = [ "Win32_UI_WindowsAndMessaging", ] -# TODO livekit https://github.com/RustAudio/cpal/pull/891 -[patch.crates-io] -cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" } - [profile.dev] split-debuginfo = "unpacked" debug = "limited" diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index 3ac55078e7..974c860c08 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -27,7 +27,6 @@ anyhow.workspace = true audio.workspace = true client.workspace = true collections.workspace = true -feature_flags.workspace = true fs.workspace = true futures.workspace = true gpui.workspace = true diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index eb91ff0885..c7993f3658 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -18,11 +18,6 @@ use room::Event; use settings::Settings; use std::sync::Arc; -#[cfg(not(target_os = "windows"))] -pub use live_kit_client::play_remote_video_track; -pub use live_kit_client::{ - track::RemoteVideoTrack, RemoteVideoTrackView, RemoteVideoTrackViewEvent, -}; pub use participant::ParticipantLocation; pub use room::Room; @@ -31,10 +26,6 @@ struct GlobalActiveCall(Model); impl Global for GlobalActiveCall {} pub fn init(client: Arc, user_store: Model, cx: &mut AppContext) { - live_kit_client::init( - cx.background_executor().dispatcher.clone(), - cx.http_client(), - ); CallSettings::register(cx); let active_call = cx.new_model(|cx| ActiveCall::new(client, user_store, cx)); diff --git a/crates/call/src/participant.rs b/crates/call/src/participant.rs index 67bb95a3b7..9faefc63c3 100644 --- a/crates/call/src/participant.rs +++ b/crates/call/src/participant.rs @@ -1,17 +1,13 @@ -#![cfg_attr(target_os = "windows", allow(unused))] - use anyhow::{anyhow, Result}; -use client::{proto, ParticipantIndex, User}; +use client::ParticipantIndex; +use client::{proto, User}; use collections::HashMap; use gpui::WeakModel; -use live_kit_client::AudioStream; +pub use live_kit_client::Frame; +pub use live_kit_client::{RemoteAudioTrack, RemoteVideoTrack}; use project::Project; use std::sync::Arc; -#[cfg(not(target_os = "windows"))] -pub use live_kit_client::id::TrackSid; -pub use live_kit_client::track::{RemoteAudioTrack, RemoteVideoTrack}; - #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ParticipantLocation { SharedProject { project_id: u64 }, @@ -43,6 +39,7 @@ pub struct LocalParticipant { pub role: proto::ChannelRole, } +#[derive(Clone, Debug)] pub struct RemoteParticipant { pub user: Arc, pub peer_id: proto::PeerId, @@ -52,17 +49,6 @@ pub struct RemoteParticipant { pub participant_index: ParticipantIndex, pub muted: bool, pub speaking: bool, - #[cfg(not(target_os = "windows"))] - pub video_tracks: HashMap, - #[cfg(not(target_os = "windows"))] - pub audio_tracks: HashMap, -} - -impl RemoteParticipant { - pub fn has_video_tracks(&self) -> bool { - #[cfg(not(target_os = "windows"))] - return !self.video_tracks.is_empty(); - #[cfg(target_os = "windows")] - return false; - } + pub video_tracks: HashMap>, + pub audio_tracks: HashMap>, } diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index a0e9de201a..3eb98f3109 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -1,5 +1,3 @@ -#![cfg_attr(target_os = "windows", allow(unused))] - use crate::{ call_settings::CallSettings, participant::{LocalParticipant, ParticipantLocation, RemoteParticipant}, @@ -17,23 +15,11 @@ use gpui::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, }; use language::LanguageRegistry; -use live_kit_client as livekit; -#[cfg(not(target_os = "windows"))] -use livekit::{ - capture_local_audio_track, capture_local_video_track, - id::ParticipantIdentity, - options::{TrackPublishOptions, VideoCodec}, - play_remote_audio_track, - publication::LocalTrackPublication, - track::{TrackKind, TrackSource}, - RoomEvent, RoomOptions, -}; -#[cfg(target_os = "windows")] -use livekit::{publication::LocalTrackPublication, RoomEvent}; +use live_kit_client::{LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RoomUpdate}; use postage::{sink::Sink, stream::Stream, watch}; use project::Project; use settings::Settings as _; -use std::{any::Any, future::Future, mem, sync::Arc, time::Duration}; +use std::{future::Future, mem, sync::Arc, time::Duration}; use util::{post_inc, ResultExt, TryFutureExt}; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); @@ -106,10 +92,13 @@ impl Room { !self.shared_projects.is_empty() } - #[cfg(all(any(test, feature = "test-support"), not(target_os = "windows")))] + #[cfg(any(test, feature = "test-support"))] pub fn is_connected(&self) -> bool { if let Some(live_kit) = self.live_kit.as_ref() { - live_kit.room.connection_state() == livekit::ConnectionState::Connected + matches!( + *live_kit.room.status().borrow(), + live_kit_client::ConnectionState::Connected { .. } + ) } else { false } @@ -123,7 +112,77 @@ impl Room { user_store: Model, cx: &mut ModelContext, ) -> Self { - spawn_room_connection(live_kit_connection_info, cx); + let live_kit_room = if let Some(connection_info) = live_kit_connection_info { + let room = live_kit_client::Room::new(); + let mut status = room.status(); + // Consume the initial status of the room. + let _ = status.try_recv(); + let _maintain_room = cx.spawn(|this, mut cx| async move { + while let Some(status) = status.next().await { + let this = if let Some(this) = this.upgrade() { + this + } else { + break; + }; + + if status == live_kit_client::ConnectionState::Disconnected { + this.update(&mut cx, |this, cx| this.leave(cx).log_err()) + .ok(); + break; + } + } + }); + + let _handle_updates = cx.spawn({ + let room = room.clone(); + move |this, mut cx| async move { + let mut updates = room.updates(); + while let Some(update) = updates.next().await { + let this = if let Some(this) = this.upgrade() { + this + } else { + break; + }; + + this.update(&mut cx, |this, cx| { + this.live_kit_room_updated(update, cx).log_err() + }) + .ok(); + } + } + }); + + let connect = room.connect(&connection_info.server_url, &connection_info.token); + cx.spawn(|this, mut cx| async move { + connect.await?; + this.update(&mut cx, |this, cx| { + if this.can_use_microphone() { + if let Some(live_kit) = &this.live_kit { + if !live_kit.muted_by_user && !live_kit.deafened { + return this.share_microphone(cx); + } + } + } + Task::ready(Ok(())) + })? + .await + }) + .detach_and_log_err(cx); + + Some(LiveKitRoom { + room, + screen_track: LocalTrack::None, + microphone_track: LocalTrack::None, + next_publish_id: 0, + muted_by_user: Self::mute_on_join(cx), + deafened: false, + speaking: false, + _maintain_room, + _handle_updates, + }) + } else { + None + }; let maintain_connection = cx.spawn({ let client = client.clone(); @@ -137,7 +196,7 @@ impl Room { Self { id, channel_id, - live_kit: None, + live_kit: live_kit_room, status: RoomStatus::Online, shared_projects: Default::default(), joined_projects: Default::default(), @@ -647,45 +706,11 @@ impl Room { this.update(&mut cx, |this, cx| this.apply_room_update(room, cx))? } - fn apply_room_update(&mut self, room: proto::Room, cx: &mut ModelContext) -> Result<()> { - log::trace!( - "client {:?}. room update: {:?}", - self.client.user_id(), - &room - ); - - self.pending_room_update = Some(self.start_room_connection(room, cx)); - - cx.notify(); - Ok(()) - } - - pub fn room_update_completed(&mut self) -> impl Future { - let mut done_rx = self.room_update_completed_rx.clone(); - async move { - while let Some(result) = done_rx.next().await { - if result.is_some() { - break; - } - } - } - } - - #[cfg(target_os = "windows")] - fn start_room_connection( - &self, + fn apply_room_update( + &mut self, mut room: proto::Room, cx: &mut ModelContext, - ) -> Task<()> { - Task::ready(()) - } - - #[cfg(not(target_os = "windows"))] - fn start_room_connection( - &self, - mut room: proto::Room, - cx: &mut ModelContext, - ) -> Task<()> { + ) -> Result<()> { // Filter ourselves out from the room's participants. let local_participant_ix = room .participants @@ -712,7 +737,8 @@ impl Room { user_store.get_users(pending_participant_user_ids, cx), ) }); - cx.spawn(|this, mut cx| async move { + + self.pending_room_update = Some(cx.spawn(|this, mut cx| async move { let (remote_participants, pending_participants) = futures::join!(remote_participants, pending_participants); @@ -750,11 +776,6 @@ impl Room { this.local_participant.projects.clear(); } - let livekit_participants = this - .live_kit - .as_ref() - .map(|live_kit| live_kit.room.remote_participants()); - if let Some(participants) = remote_participants.log_err() { for (participant, user) in room.participants.into_iter().zip(participants) { let Some(peer_id) = participant.peer_id else { @@ -837,31 +858,40 @@ impl Room { muted: true, speaking: false, video_tracks: Default::default(), - #[cfg(not(target_os = "windows"))] audio_tracks: Default::default(), }, ); Audio::play_sound(Sound::Joined, cx); - if let Some(livekit_participants) = &livekit_participants { - if let Some(livekit_participant) = livekit_participants - .get(&ParticipantIdentity(user.id.to_string())) + + if let Some(live_kit) = this.live_kit.as_ref() { + let video_tracks = + live_kit.room.remote_video_tracks(&user.id.to_string()); + let audio_tracks = + live_kit.room.remote_audio_tracks(&user.id.to_string()); + let publications = live_kit + .room + .remote_audio_track_publications(&user.id.to_string()); + + for track in video_tracks { + this.live_kit_room_updated( + RoomUpdate::SubscribedToRemoteVideoTrack(track), + cx, + ) + .log_err(); + } + + for (track, publication) in + audio_tracks.iter().zip(publications.iter()) { - for publication in - livekit_participant.track_publications().into_values() - { - if let Some(track) = publication.track() { - this.live_kit_room_updated( - RoomEvent::TrackSubscribed { - track, - publication, - participant: livekit_participant.clone(), - }, - cx, - ) - .warn_on_err(); - } - } + this.live_kit_room_updated( + RoomUpdate::SubscribedToRemoteAudioTrack( + track.clone(), + publication.clone(), + ), + cx, + ) + .log_err(); } } } @@ -929,89 +959,61 @@ impl Room { cx.notify(); }) .ok(); - }) + })); + + cx.notify(); + Ok(()) + } + + pub fn room_update_completed(&mut self) -> impl Future { + let mut done_rx = self.room_update_completed_rx.clone(); + async move { + while let Some(result) = done_rx.next().await { + if result.is_some() { + break; + } + } + } } fn live_kit_room_updated( &mut self, - event: RoomEvent, + update: RoomUpdate, cx: &mut ModelContext, ) -> Result<()> { - log::trace!( - "client {:?}. livekit event: {:?}", - self.client.user_id(), - &event - ); - - match event { - #[cfg(not(target_os = "windows"))] - RoomEvent::TrackSubscribed { - track, - participant, - publication, - } => { - let user_id = participant.identity().0.parse()?; - let track_id = track.sid(); - let participant = self.remote_participants.get_mut(&user_id).ok_or_else(|| { - anyhow!( - "{:?} subscribed to track by unknown participant {user_id}", - self.client.user_id() - ) - })?; - if self.live_kit.as_ref().map_or(true, |kit| kit.deafened) { - track.rtc_track().set_enabled(false); - } - match track { - livekit::track::RemoteTrack::Audio(track) => { - cx.emit(Event::RemoteAudioTracksChanged { - participant_id: participant.peer_id, - }); - let stream = play_remote_audio_track(&track, cx); - participant.audio_tracks.insert(track_id, (track, stream)); - participant.muted = publication.is_muted(); - } - livekit::track::RemoteTrack::Video(track) => { - cx.emit(Event::RemoteVideoTracksChanged { - participant_id: participant.peer_id, - }); - participant.video_tracks.insert(track_id, track); - } - } + match update { + RoomUpdate::SubscribedToRemoteVideoTrack(track) => { + let user_id = track.publisher_id().parse()?; + let track_id = track.sid().to_string(); + let participant = self + .remote_participants + .get_mut(&user_id) + .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; + participant.video_tracks.insert(track_id.clone(), track); + cx.emit(Event::RemoteVideoTracksChanged { + participant_id: participant.peer_id, + }); } - #[cfg(not(target_os = "windows"))] - RoomEvent::TrackUnsubscribed { - track, participant, .. + RoomUpdate::UnsubscribedFromRemoteVideoTrack { + publisher_id, + track_id, } => { - let user_id = participant.identity().0.parse()?; - let participant = self.remote_participants.get_mut(&user_id).ok_or_else(|| { - anyhow!( - "{:?}, unsubscribed from track by unknown participant {user_id}", - self.client.user_id() - ) - })?; - match track { - livekit::track::RemoteTrack::Audio(track) => { - participant.audio_tracks.remove(&track.sid()); - participant.muted = true; - cx.emit(Event::RemoteAudioTracksChanged { - participant_id: participant.peer_id, - }); - } - livekit::track::RemoteTrack::Video(track) => { - participant.video_tracks.remove(&track.sid()); - cx.emit(Event::RemoteVideoTracksChanged { - participant_id: participant.peer_id, - }); - } - } + let user_id = publisher_id.parse()?; + let participant = self + .remote_participants + .get_mut(&user_id) + .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?; + participant.video_tracks.remove(&track_id); + cx.emit(Event::RemoteVideoTracksChanged { + participant_id: participant.peer_id, + }); } - #[cfg(not(target_os = "windows"))] - RoomEvent::ActiveSpeakersChanged { speakers } => { + RoomUpdate::ActiveSpeakersChanged { speakers } => { let mut speaker_ids = speakers .into_iter() - .filter_map(|speaker| speaker.identity().0.parse().ok()) + .filter_map(|speaker_sid| speaker_sid.parse().ok()) .collect::>(); speaker_ids.sort_unstable(); for (sid, participant) in &mut self.remote_participants { @@ -1024,65 +1026,82 @@ impl Room { } } - #[cfg(not(target_os = "windows"))] - RoomEvent::TrackMuted { - participant, - publication, - } - | RoomEvent::TrackUnmuted { - participant, - publication, - } => { + RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } => { let mut found = false; - let user_id = participant.identity().0.parse()?; - let track_id = publication.sid(); - if let Some(participant) = self.remote_participants.get_mut(&user_id) { - for (track, _) in participant.audio_tracks.values() { + for participant in &mut self.remote_participants.values_mut() { + for track in participant.audio_tracks.values() { if track.sid() == track_id { found = true; break; } } if found { - participant.muted = publication.is_muted(); + participant.muted = muted; + break; } } } - #[cfg(not(target_os = "windows"))] - RoomEvent::LocalTrackUnpublished { publication, .. } => { - log::info!("unpublished track {}", publication.sid()); + RoomUpdate::SubscribedToRemoteAudioTrack(track, publication) => { + if let Some(live_kit) = &self.live_kit { + if live_kit.deafened { + track.stop(); + cx.foreground_executor() + .spawn(publication.set_enabled(false)) + .detach(); + } + } + + let user_id = track.publisher_id().parse()?; + let track_id = track.sid().to_string(); + let participant = self + .remote_participants + .get_mut(&user_id) + .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; + participant.audio_tracks.insert(track_id.clone(), track); + participant.muted = publication.is_muted(); + + cx.emit(Event::RemoteAudioTracksChanged { + participant_id: participant.peer_id, + }); + } + + RoomUpdate::UnsubscribedFromRemoteAudioTrack { + publisher_id, + track_id, + } => { + let user_id = publisher_id.parse()?; + let participant = self + .remote_participants + .get_mut(&user_id) + .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?; + participant.audio_tracks.remove(&track_id); + cx.emit(Event::RemoteAudioTracksChanged { + participant_id: participant.peer_id, + }); + } + + RoomUpdate::LocalAudioTrackUnpublished { publication } => { + log::info!("unpublished audio track {}", publication.sid()); if let Some(room) = &mut self.live_kit { - if let LocalTrack::Published { - track_publication, .. - } = &room.microphone_track - { - if track_publication.sid() == publication.sid() { - room.microphone_track = LocalTrack::None; - } - } - if let LocalTrack::Published { - track_publication, .. - } = &room.screen_track - { - if track_publication.sid() == publication.sid() { - room.screen_track = LocalTrack::None; - } - } + room.microphone_track = LocalTrack::None; } } - #[cfg(not(target_os = "windows"))] - RoomEvent::LocalTrackPublished { publication, .. } => { - log::info!("published track {:?}", publication.sid()); + RoomUpdate::LocalVideoTrackUnpublished { publication } => { + log::info!("unpublished video track {}", publication.sid()); + if let Some(room) = &mut self.live_kit { + room.screen_track = LocalTrack::None; + } } - #[cfg(not(target_os = "windows"))] - RoomEvent::Disconnected { reason } => { - log::info!("disconnected from room: {reason:?}"); - self.leave(cx).detach_and_log_err(cx); + RoomUpdate::LocalAudioTrackPublished { publication } => { + log::info!("published audio track {}", publication.sid()); + } + + RoomUpdate::LocalVideoTrackPublished { publication } => { + log::info!("published video track {}", publication.sid()); } - _ => {} } cx.notify(); @@ -1298,17 +1317,8 @@ impl Room { self.live_kit.as_ref().map(|live_kit| live_kit.deafened) } - pub fn can_use_microphone(&self, _cx: &AppContext) -> bool { + pub fn can_use_microphone(&self) -> bool { use proto::ChannelRole::*; - - #[cfg(not(any(test, feature = "test-support")))] - { - use feature_flags::FeatureFlagAppExt as _; - if cfg!(target_os = "windows") || (cfg!(target_os = "linux") && !_cx.is_staff()) { - return false; - } - } - match self.local_participant.role { Admin | Member | Talker => true, Guest | Banned => false, @@ -1323,177 +1333,161 @@ impl Room { } } - #[cfg(target_os = "windows")] - pub fn share_microphone(&mut self, cx: &mut ModelContext) -> Task> { - Task::ready(Err(anyhow!("Windows is not supported yet"))) - } - - #[cfg(not(target_os = "windows"))] #[track_caller] pub fn share_microphone(&mut self, cx: &mut ModelContext) -> Task> { if self.status.is_offline() { return Task::ready(Err(anyhow!("room is offline"))); } - let (participant, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { + let publish_id = if let Some(live_kit) = self.live_kit.as_mut() { let publish_id = post_inc(&mut live_kit.next_publish_id); live_kit.microphone_track = LocalTrack::Pending { publish_id }; cx.notify(); - (live_kit.room.local_participant(), publish_id) + publish_id } else { return Task::ready(Err(anyhow!("live-kit was not initialized"))); }; cx.spawn(move |this, mut cx| async move { - let (track, stream) = cx.update(capture_local_audio_track)??; + let publish_track = async { + let track = LocalAudioTrack::create(); + this.upgrade() + .ok_or_else(|| anyhow!("room was dropped"))? + .update(&mut cx, |this, _| { + this.live_kit + .as_ref() + .map(|live_kit| live_kit.room.publish_audio_track(track)) + })? + .ok_or_else(|| anyhow!("live-kit was not initialized"))? + .await + }; + let publication = publish_track.await; + this.upgrade() + .ok_or_else(|| anyhow!("room was dropped"))? + .update(&mut cx, |this, cx| { + let live_kit = this + .live_kit + .as_mut() + .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - let publication = participant - .publish_track( - livekit::track::LocalTrack::Audio(track), - TrackPublishOptions { - source: TrackSource::Microphone, - ..Default::default() - }, - ) - .await - .map_err(|error| anyhow!("failed to publish track: {error}")); - this.update(&mut cx, |this, cx| { - let live_kit = this - .live_kit - .as_mut() - .ok_or_else(|| anyhow!("live-kit was not initialized"))?; + let canceled = if let LocalTrack::Pending { + publish_id: cur_publish_id, + } = &live_kit.microphone_track + { + *cur_publish_id != publish_id + } else { + true + }; - let canceled = if let LocalTrack::Pending { - publish_id: cur_publish_id, - } = &live_kit.microphone_track - { - *cur_publish_id != publish_id - } else { - true - }; - - match publication { - Ok(publication) => { - if canceled { - cx.background_executor() - .spawn(async move { - participant.unpublish_track(&publication.sid()).await - }) - .detach_and_log_err(cx) - } else { - if live_kit.muted_by_user || live_kit.deafened { - publication.mute(); + match publication { + Ok(publication) => { + if canceled { + live_kit.room.unpublish_track(publication); + } else { + if live_kit.muted_by_user || live_kit.deafened { + cx.background_executor() + .spawn(publication.set_mute(true)) + .detach(); + } + live_kit.microphone_track = LocalTrack::Published { + track_publication: publication, + }; + cx.notify(); } - live_kit.microphone_track = LocalTrack::Published { - track_publication: publication, - _stream: Box::new(stream), - }; - cx.notify(); - } - Ok(()) - } - Err(error) => { - if canceled { Ok(()) - } else { - live_kit.microphone_track = LocalTrack::None; - cx.notify(); - Err(error) + } + Err(error) => { + if canceled { + Ok(()) + } else { + live_kit.microphone_track = LocalTrack::None; + cx.notify(); + Err(error) + } } } - } - })? + })? }) } - #[cfg(target_os = "windows")] - pub fn share_screen(&mut self, cx: &mut ModelContext) -> Task> { - Task::ready(Err(anyhow!("Windows is not supported yet"))) - } - - #[cfg(not(target_os = "windows"))] pub fn share_screen(&mut self, cx: &mut ModelContext) -> Task> { if self.status.is_offline() { return Task::ready(Err(anyhow!("room is offline"))); - } - if self.is_screen_sharing() { + } else if self.is_screen_sharing() { return Task::ready(Err(anyhow!("screen was already shared"))); } - let (participant, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { + let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { let publish_id = post_inc(&mut live_kit.next_publish_id); live_kit.screen_track = LocalTrack::Pending { publish_id }; cx.notify(); - (live_kit.room.local_participant(), publish_id) + (live_kit.room.display_sources(), publish_id) } else { return Task::ready(Err(anyhow!("live-kit was not initialized"))); }; - let sources = cx.screen_capture_sources(); - cx.spawn(move |this, mut cx| async move { - let sources = sources.await??; - let source = sources.first().ok_or_else(|| anyhow!("no display found"))?; + let publish_track = async { + let displays = displays.await?; + let display = displays + .first() + .ok_or_else(|| anyhow!("no display found"))?; + let track = LocalVideoTrack::screen_share_for_display(display); + this.upgrade() + .ok_or_else(|| anyhow!("room was dropped"))? + .update(&mut cx, |this, _| { + this.live_kit + .as_ref() + .map(|live_kit| live_kit.room.publish_video_track(track)) + })? + .ok_or_else(|| anyhow!("live-kit was not initialized"))? + .await + }; - let (track, stream) = capture_local_video_track(&**source).await?; + let publication = publish_track.await; + this.upgrade() + .ok_or_else(|| anyhow!("room was dropped"))? + .update(&mut cx, |this, cx| { + let live_kit = this + .live_kit + .as_mut() + .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - let publication = participant - .publish_track( - livekit::track::LocalTrack::Video(track), - TrackPublishOptions { - source: TrackSource::Screenshare, - video_codec: VideoCodec::H264, - ..Default::default() - }, - ) - .await - .map_err(|error| anyhow!("error publishing screen track {error:?}")); + let canceled = if let LocalTrack::Pending { + publish_id: cur_publish_id, + } = &live_kit.screen_track + { + *cur_publish_id != publish_id + } else { + true + }; - this.update(&mut cx, |this, cx| { - let live_kit = this - .live_kit - .as_mut() - .ok_or_else(|| anyhow!("live-kit was not initialized"))?; + match publication { + Ok(publication) => { + if canceled { + live_kit.room.unpublish_track(publication); + } else { + live_kit.screen_track = LocalTrack::Published { + track_publication: publication, + }; + cx.notify(); + } - let canceled = if let LocalTrack::Pending { - publish_id: cur_publish_id, - } = &live_kit.screen_track - { - *cur_publish_id != publish_id - } else { - true - }; + Audio::play_sound(Sound::StartScreenshare, cx); - match publication { - Ok(publication) => { - if canceled { - cx.background_executor() - .spawn(async move { - participant.unpublish_track(&publication.sid()).await - }) - .detach() - } else { - live_kit.screen_track = LocalTrack::Published { - track_publication: publication, - _stream: Box::new(stream), - }; - cx.notify(); - } - - Audio::play_sound(Sound::StartScreenshare, cx); - Ok(()) - } - Err(error) => { - if canceled { Ok(()) - } else { - live_kit.screen_track = LocalTrack::None; - cx.notify(); - Err(error) + } + Err(error) => { + if canceled { + Ok(()) + } else { + live_kit.screen_track = LocalTrack::None; + cx.notify(); + Err(error) + } } } - } - })? + })? }) } @@ -1518,7 +1512,9 @@ impl Room { } if should_undeafen { - self.set_deafened(false, cx); + if let Some(task) = self.set_deafened(false, cx) { + task.detach_and_log_err(cx); + } } } } @@ -1531,7 +1527,9 @@ impl Room { live_kit.deafened = deafened; let should_change_mute = !live_kit.muted_by_user; - self.set_deafened(deafened, cx); + if let Some(task) = self.set_deafened(deafened, cx) { + task.detach_and_log_err(cx); + } if should_change_mute { if let Some(task) = self.set_mute(deafened, cx) { @@ -1559,36 +1557,47 @@ impl Room { LocalTrack::Published { track_publication, .. } => { - #[cfg(not(target_os = "windows"))] - { - let local_participant = live_kit.room.local_participant(); - let sid = track_publication.sid(); - cx.background_executor() - .spawn(async move { local_participant.unpublish_track(&sid).await }) - .detach_and_log_err(cx); - cx.notify(); - } + live_kit.room.unpublish_track(track_publication); + cx.notify(); + Audio::play_sound(Sound::StopScreenshare, cx); Ok(()) } } } - fn set_deafened(&mut self, deafened: bool, cx: &mut ModelContext) -> Option<()> { - #[cfg(not(target_os = "windows"))] - { - let live_kit = self.live_kit.as_mut()?; - cx.notify(); - for (_, participant) in live_kit.room.remote_participants() { - for (_, publication) in participant.track_publications() { - if publication.kind() == TrackKind::Audio { - publication.set_enabled(!deafened); - } + fn set_deafened( + &mut self, + deafened: bool, + cx: &mut ModelContext, + ) -> Option>> { + let live_kit = self.live_kit.as_mut()?; + cx.notify(); + + let mut track_updates = Vec::new(); + for participant in self.remote_participants.values() { + for publication in live_kit + .room + .remote_audio_track_publications(&participant.user.id.to_string()) + { + track_updates.push(publication.set_enabled(!deafened)); + } + + for track in participant.audio_tracks.values() { + if deafened { + track.stop(); + } else { + track.start(); } } } - None + Some(cx.foreground_executor().spawn(async move { + for result in futures::future::join_all(track_updates).await { + result?; + } + Ok(()) + })) } fn set_mute( @@ -1614,84 +1623,25 @@ impl Room { } } LocalTrack::Pending { .. } => None, - LocalTrack::Published { - track_publication, .. - } => { - #[cfg(not(target_os = "windows"))] - { - if should_mute { - track_publication.mute() - } else { - track_publication.unmute() - } - } - None - } + LocalTrack::Published { track_publication } => Some( + cx.foreground_executor() + .spawn(track_publication.set_mute(should_mute)), + ), } } -} -#[cfg(target_os = "windows")] -fn spawn_room_connection( - live_kit_connection_info: Option, - cx: &mut ModelContext<'_, Room>, -) { -} - -#[cfg(not(target_os = "windows"))] -fn spawn_room_connection( - live_kit_connection_info: Option, - cx: &mut ModelContext<'_, Room>, -) { - if let Some(connection_info) = live_kit_connection_info { - cx.spawn(|this, mut cx| async move { - let (room, mut events) = livekit::Room::connect( - &connection_info.server_url, - &connection_info.token, - RoomOptions::default(), - ) - .await?; - - this.update(&mut cx, |this, cx| { - let _handle_updates = cx.spawn(|this, mut cx| async move { - while let Some(event) = events.recv().await { - if this - .update(&mut cx, |this, cx| { - this.live_kit_room_updated(event, cx).warn_on_err(); - }) - .is_err() - { - break; - } - } - }); - - let muted_by_user = Room::mute_on_join(cx); - this.live_kit = Some(LiveKitRoom { - room: Arc::new(room), - screen_track: LocalTrack::None, - microphone_track: LocalTrack::None, - next_publish_id: 0, - muted_by_user, - deafened: false, - speaking: false, - _handle_updates, - }); - - if !muted_by_user && this.can_use_microphone(cx) { - this.share_microphone(cx) - } else { - Task::ready(Ok(())) - } - })? - .await - }) - .detach_and_log_err(cx); + #[cfg(any(test, feature = "test-support"))] + pub fn set_display_sources(&self, sources: Vec) { + self.live_kit + .as_ref() + .unwrap() + .room + .set_display_sources(sources); } } struct LiveKitRoom { - room: Arc, + room: Arc, screen_track: LocalTrack, microphone_track: LocalTrack, /// Tracks whether we're currently in a muted state due to auto-mute from deafening or manual mute performed by user. @@ -1699,21 +1649,17 @@ struct LiveKitRoom { deafened: bool, speaking: bool, next_publish_id: usize, + _maintain_room: Task<()>, _handle_updates: Task<()>, } impl LiveKitRoom { - #[cfg(target_os = "windows")] - fn stop_publishing(&mut self, _cx: &mut ModelContext) {} - - #[cfg(not(target_os = "windows"))] fn stop_publishing(&mut self, cx: &mut ModelContext) { - let mut tracks_to_unpublish = Vec::new(); if let LocalTrack::Published { track_publication, .. } = mem::replace(&mut self.microphone_track, LocalTrack::None) { - tracks_to_unpublish.push(track_publication.sid()); + self.room.unpublish_track(track_publication); cx.notify(); } @@ -1721,18 +1667,9 @@ impl LiveKitRoom { track_publication, .. } = mem::replace(&mut self.screen_track, LocalTrack::None) { - tracks_to_unpublish.push(track_publication.sid()); + self.room.unpublish_track(track_publication); cx.notify(); } - - let participant = self.room.local_participant(); - cx.background_executor() - .spawn(async move { - for sid in tracks_to_unpublish { - participant.unpublish_track(&sid).await.log_err(); - } - }) - .detach(); } } @@ -1743,7 +1680,6 @@ enum LocalTrack { }, Published { track_publication: LocalTrackPublication, - _stream: Box, }, } diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index 2ce69efc9b..29373bc6ea 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -1,6 +1,3 @@ -// todo(windows): Actually run the tests -#![cfg(not(target_os = "windows"))] - use std::sync::Arc; use call::Room; diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 006a3e5d1c..5a091fe308 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -107,9 +107,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test }); assert!(project_b.read_with(cx_b, |project, cx| project.is_read_only(cx))); assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); - cx_b.update(|cx_b| { - assert!(room_b.read_with(cx_b, |room, cx| !room.can_use_microphone(cx))); - }); + assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone())); assert!(room_b .update(cx_b, |room, cx| room.share_microphone(cx)) .await @@ -135,9 +133,7 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test assert!(editor_b.update(cx_b, |editor, cx| !editor.read_only(cx))); // B sees themselves as muted, and can unmute. - cx_b.update(|cx_b| { - assert!(room_b.read_with(cx_b, |room, cx| room.can_use_microphone(cx))); - }); + assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone())); room_b.read_with(cx_b, |room, _| assert!(room.is_muted())); room_b.update(cx_b, |room, cx| room.toggle_mute(cx)); cx_a.run_until_parked(); @@ -230,9 +226,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes let room_b = cx_b .read(ActiveCall::global) .update(cx_b, |call, _| call.room().unwrap().clone()); - cx_b.update(|cx_b| { - assert!(room_b.read_with(cx_b, |room, cx| !room.can_use_microphone(cx))); - }); + assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone())); // A tries to grant write access to B, but cannot because B has not // yet signed the zed CLA. @@ -250,9 +244,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes .unwrap_err(); cx_a.run_until_parked(); assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects())); - cx_b.update(|cx_b| { - assert!(room_b.read_with(cx_b, |room, cx| !room.can_use_microphone(cx))); - }); + assert!(room_b.read_with(cx_b, |room, _| !room.can_use_microphone())); // A tries to grant write access to B, but cannot because B has not // yet signed the zed CLA. @@ -270,9 +262,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes .unwrap(); cx_a.run_until_parked(); assert!(room_b.read_with(cx_b, |room, _| !room.can_share_projects())); - cx_b.update(|cx_b| { - assert!(room_b.read_with(cx_b, |room, cx| room.can_use_microphone(cx))); - }); + assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone())); // User B signs the zed CLA. server @@ -297,7 +287,5 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes .unwrap(); cx_a.run_until_parked(); assert!(room_b.read_with(cx_b, |room, _| room.can_share_projects())); - cx_b.update(|cx_b| { - assert!(room_b.read_with(cx_b, |room, cx| room.can_use_microphone(cx))); - }); + assert!(room_b.read_with(cx_b, |room, _| room.can_use_microphone())); } diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 778d67b81d..d708194f58 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -9,9 +9,10 @@ use collab_ui::{ use editor::{Editor, ExcerptRange, MultiBuffer}; use gpui::{ point, BackgroundExecutor, BorrowAppContext, Context, Entity, SharedString, TestAppContext, - TestScreenCaptureSource, View, VisualContext, VisualTestContext, + View, VisualContext, VisualTestContext, }; use language::Capability; +use live_kit_client::MacOSDisplay; use project::WorktreeSettings; use rpc::proto::PeerId; use serde_json::json; @@ -428,17 +429,17 @@ async fn test_basic_following( ); // Client B activates an external window, which causes a new screen-sharing item to be added to the pane. - let display = TestScreenCaptureSource::new(); + let display = MacOSDisplay::new(); active_call_b .update(cx_b, |call, cx| call.set_location(None, cx)) .await .unwrap(); - cx_b.set_screen_capture_sources(vec![display]); active_call_b .update(cx_b, |call, cx| { - call.room() - .unwrap() - .update(cx, |room, cx| room.share_screen(cx)) + call.room().unwrap().update(cx, |room, cx| { + room.set_display_sources(vec![display.clone()]); + room.share_screen(cx) + }) }) .await .unwrap(); diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 94b7ad81c5..5ec9a574a1 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -15,7 +15,7 @@ use futures::{channel::mpsc, StreamExt as _}; use git::repository::GitFileStatus; use gpui::{ px, size, AppContext, BackgroundExecutor, Model, Modifiers, MouseButton, MouseDownEvent, - TestAppContext, TestScreenCaptureSource, UpdateGlobal, + TestAppContext, UpdateGlobal, }; use language::{ language_settings::{ @@ -24,6 +24,7 @@ use language::{ tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope, }; +use live_kit_client::MacOSDisplay; use lsp::LanguageServerId; use parking_lot::Mutex; use project::lsp_store::FormatTarget; @@ -240,15 +241,15 @@ async fn test_basic_calls( ); // User A shares their screen - let display = TestScreenCaptureSource::new(); + let display = MacOSDisplay::new(); let events_b = active_call_events(cx_b); let events_c = active_call_events(cx_c); - cx_a.set_screen_capture_sources(vec![display]); active_call_a .update(cx_a, |call, cx| { - call.room() - .unwrap() - .update(cx, |room, cx| room.share_screen(cx)) + call.room().unwrap().update(cx, |room, cx| { + room.set_display_sources(vec![display.clone()]); + room.share_screen(cx) + }) }) .await .unwrap(); @@ -1941,7 +1942,7 @@ async fn test_mute_deafen( room_a.read_with(cx_a, |room, _| assert!(!room.is_muted())); room_b.read_with(cx_b, |room, _| assert!(!room.is_muted())); - // Users A and B are both unmuted. + // Users A and B are both muted. assert_eq!( participant_audio_state(&room_a, cx_a), &[ParticipantAudioState { @@ -2073,7 +2074,7 @@ async fn test_mute_deafen( audio_tracks_playing: participant .audio_tracks .values() - .map(|(track, _)| track.rtc_track().enabled()) + .map(|track| track.is_playing()) .collect(), }) .collect::>() @@ -6056,13 +6057,13 @@ async fn test_join_call_after_screen_was_shared( assert_eq!(call_b.calling_user.github_login, "user_a"); // User A shares their screen - let display = TestScreenCaptureSource::new(); - cx_a.set_screen_capture_sources(vec![display]); + let display = MacOSDisplay::new(); active_call_a .update(cx_a, |call, cx| { - call.room() - .unwrap() - .update(cx, |room, cx| room.share_screen(cx)) + call.room().unwrap().update(cx, |room, cx| { + room.set_display_sources(vec![display.clone()]); + room.share_screen(cx) + }) }) .await .unwrap(); diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index ae9e101031..17cd1b51c4 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -47,7 +47,7 @@ use workspace::{Workspace, WorkspaceStore}; pub struct TestServer { pub app_state: Arc, - pub test_live_kit_server: Arc, + pub test_live_kit_server: Arc, server: Arc, next_github_user_id: i32, connection_killers: Arc>>>, @@ -89,7 +89,7 @@ impl TestServer { TestDb::sqlite(deterministic.clone()) }; let live_kit_server_id = NEXT_LIVE_KIT_SERVER_ID.fetch_add(1, SeqCst); - let live_kit_server = live_kit_client::test::TestServer::create( + let live_kit_server = live_kit_client::TestServer::create( format!("http://livekit.{}.test", live_kit_server_id), format!("devkey-{}", live_kit_server_id), format!("secret-{}", live_kit_server_id), @@ -499,7 +499,7 @@ impl TestServer { pub async fn build_app_state( test_db: &TestDb, - live_kit_test_server: &live_kit_client::test::TestServer, + live_kit_test_server: &live_kit_client::TestServer, executor: Executor, ) -> Arc { Arc::new(AppState { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index fa3ab0219b..c93a48096a 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -474,10 +474,11 @@ impl CollabPanel { project_id: project.id, worktree_root_names: project.worktree_root_names.clone(), host_user_id: participant.user.id, - is_last: projects.peek().is_none() && !participant.has_video_tracks(), + is_last: projects.peek().is_none() + && participant.video_tracks.is_empty(), }); } - if participant.has_video_tracks() { + if !participant.video_tracks.is_empty() { self.entries.push(ListEntry::ParticipantScreen { peer_id: Some(participant.peer_id), is_last: true, diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index e5917a0f05..5a015106c7 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -48,7 +48,6 @@ mod macos { fn generate_dispatch_bindings() { println!("cargo:rustc-link-lib=framework=System"); - println!("cargo:rustc-link-lib=framework=ScreenCaptureKit"); println!("cargo:rerun-if-changed=src/platform/mac/dispatch.h"); let bindings = bindgen::Builder::default() diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 50cadeff8f..0776e5c72e 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -33,8 +33,8 @@ use crate::{ Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation, - ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, - View, ViewContext, Window, WindowAppearance, WindowContext, WindowHandle, WindowId, + SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, + Window, WindowAppearance, WindowContext, WindowHandle, WindowId, }; mod async_context; @@ -599,13 +599,6 @@ impl AppContext { self.platform.primary_display() } - /// Returns a list of available screen capture sources. - pub fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>> { - self.platform.screen_capture_sources() - } - /// Returns the display with the given ID, if one exists. pub fn find_display(&self, id: DisplayId) -> Option> { self.displays() diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 2f5053a382..34449c91ec 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -4,8 +4,8 @@ use crate::{ Element, Empty, Entity, EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher, - TestPlatform, TestScreenCaptureSource, TestWindow, TextSystem, View, ViewContext, - VisualContext, WindowBounds, WindowContext, WindowHandle, WindowOptions, + TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext, WindowBounds, + WindowContext, WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{channel::oneshot, Stream, StreamExt}; @@ -287,12 +287,6 @@ impl TestAppContext { self.test_window(window_handle).simulate_resize(size); } - /// Causes the given sources to be returned if the application queries for screen - /// capture sources. - pub fn set_screen_capture_sources(&self, sources: Vec) { - self.test_platform.set_screen_capture_sources(sources); - } - /// Returns all windows open in the test. pub fn windows(&self) -> Vec { self.app.borrow().windows().clone() diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index b636c95a61..9e0b9b9014 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -704,11 +704,6 @@ pub struct Bounds { pub size: Size, } -/// Create a bounds with the given origin and size -pub fn bounds(origin: Point, size: Size) -> Bounds { - Bounds { origin, size } -} - impl Bounds { /// Generate a centered bounds for the given display or primary display if none is provided pub fn centered(display_id: Option, size: Size, cx: &AppContext) -> Self { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 3e4d0706f0..d9016afb68 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -70,9 +70,6 @@ pub(crate) use test::*; #[cfg(target_os = "windows")] pub(crate) use windows::*; -#[cfg(any(test, feature = "test-support"))] -pub use test::TestScreenCaptureSource; - #[cfg(target_os = "macos")] pub(crate) fn current_platform(headless: bool) -> Rc { Rc::new(MacPlatform::new(headless)) @@ -152,10 +149,6 @@ pub(crate) trait Platform: 'static { None } - fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>>; - fn open_window( &self, handle: AnyWindowHandle, @@ -235,25 +228,6 @@ pub trait PlatformDisplay: Send + Sync + Debug { } } -/// A source of on-screen video content that can be captured. -pub trait ScreenCaptureSource { - /// Returns the video resolution of this source. - fn resolution(&self) -> Result>; - - /// Start capture video from this source, invoking the given callback - /// with each frame. - fn stream( - &self, - frame_callback: Box, - ) -> oneshot::Receiver>>; -} - -/// A video stream captured from a screen. -pub trait ScreenCaptureStream {} - -/// A frame of video captured from a screen. -pub struct ScreenCaptureFrame(pub PlatformScreenCaptureFrame); - /// An opaque identifier for a hardware display #[derive(PartialEq, Eq, Hash, Copy, Clone)] pub struct DisplayId(pub(crate) u32); diff --git a/crates/gpui/src/platform/linux.rs b/crates/gpui/src/platform/linux.rs index 089b52cf1e..0499869361 100644 --- a/crates/gpui/src/platform/linux.rs +++ b/crates/gpui/src/platform/linux.rs @@ -20,5 +20,3 @@ pub(crate) use text_system::*; pub(crate) use wayland::*; #[cfg(feature = "x11")] pub(crate) use x11::*; - -pub(crate) type PlatformScreenCaptureFrame = (); diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 5865e50092..a2e9af691b 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -35,8 +35,8 @@ use crate::{ px, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, Keymap, Keystroke, LinuxDispatcher, Menu, MenuItem, Modifiers, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformInputHandler, PlatformTextSystem, - PlatformWindow, Point, PromptLevel, Result, ScreenCaptureSource, SemanticVersion, SharedString, - Size, Task, WindowAppearance, WindowOptions, WindowParams, + PlatformWindow, Point, PromptLevel, Result, SemanticVersion, SharedString, Size, Task, + WindowAppearance, WindowOptions, WindowParams, }; pub(crate) const SCROLL_LINES: f32 = 3.0; @@ -242,14 +242,6 @@ impl Platform for P { self.displays() } - fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>> { - let (mut tx, rx) = oneshot::channel(); - tx.send(Err(anyhow!("screen capture not implemented"))).ok(); - rx - } - fn active_window(&self) -> Option { self.active_window() } diff --git a/crates/gpui/src/platform/mac.rs b/crates/gpui/src/platform/mac.rs index bd3d8f35ac..396fd49d04 100644 --- a/crates/gpui/src/platform/mac.rs +++ b/crates/gpui/src/platform/mac.rs @@ -4,14 +4,12 @@ mod dispatcher; mod display; mod display_link; mod events; -mod screen_capture; #[cfg(not(feature = "macos-blade"))] mod metal_atlas; #[cfg(not(feature = "macos-blade"))] pub mod metal_renderer; -use media::core_video::CVImageBuffer; #[cfg(not(feature = "macos-blade"))] use metal_renderer as renderer; @@ -51,9 +49,6 @@ pub(crate) use window::*; #[cfg(feature = "font-kit")] pub(crate) use text_system::*; -/// A frame of video captured from a screen. -pub(crate) type PlatformScreenCaptureFrame = CVImageBuffer; - trait BoolExt { fn to_objc(self) -> BOOL; } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index d0fd8a85f4..b744c658ce 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -1,14 +1,14 @@ use super::{ attributed_string::{NSAttributedString, NSMutableAttributedString}, events::key_to_native, - renderer, screen_capture, BoolExt, + BoolExt, }; use crate::{ hash, Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem, ClipboardString, CursorStyle, ForegroundExecutor, Image, ImageFormat, Keymap, MacDispatcher, MacDisplay, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, - PlatformTextSystem, PlatformWindow, Result, ScreenCaptureSource, SemanticVersion, Task, - WindowAppearance, WindowParams, + PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowAppearance, + WindowParams, }; use anyhow::anyhow; use block::ConcreteBlock; @@ -58,6 +58,8 @@ use std::{ }; use strum::IntoEnumIterator; +use super::renderer; + #[allow(non_upper_case_globals)] const NSUTF8StringEncoding: NSUInteger = 4; @@ -548,12 +550,6 @@ impl Platform for MacPlatform { .collect() } - fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>> { - screen_capture::get_sources() - } - fn active_window(&self) -> Option { MacWindow::active_window() } diff --git a/crates/gpui/src/platform/mac/screen_capture.rs b/crates/gpui/src/platform/mac/screen_capture.rs deleted file mode 100644 index a2b535996f..0000000000 --- a/crates/gpui/src/platform/mac/screen_capture.rs +++ /dev/null @@ -1,239 +0,0 @@ -use crate::{ - platform::{ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream}, - px, size, Pixels, Size, -}; -use anyhow::{anyhow, Result}; -use block::ConcreteBlock; -use cocoa::{ - base::{id, nil, YES}, - foundation::NSArray, -}; -use core_foundation::base::TCFType; -use ctor::ctor; -use futures::channel::oneshot; -use media::core_media::{CMSampleBuffer, CMSampleBufferRef}; -use metal::NSInteger; -use objc::{ - class, - declare::ClassDecl, - msg_send, - runtime::{Class, Object, Sel}, - sel, sel_impl, -}; -use std::{cell::RefCell, ffi::c_void, mem, ptr, rc::Rc}; - -#[derive(Clone)] -pub struct MacScreenCaptureSource { - sc_display: id, -} - -pub struct MacScreenCaptureStream { - sc_stream: id, - sc_stream_output: id, -} - -#[link(name = "ScreenCaptureKit", kind = "framework")] -extern "C" {} - -static mut DELEGATE_CLASS: *const Class = ptr::null(); -static mut OUTPUT_CLASS: *const Class = ptr::null(); -const FRAME_CALLBACK_IVAR: &str = "frame_callback"; - -#[allow(non_upper_case_globals)] -const SCStreamOutputTypeScreen: NSInteger = 0; - -impl ScreenCaptureSource for MacScreenCaptureSource { - fn resolution(&self) -> Result> { - unsafe { - let width: i64 = msg_send![self.sc_display, width]; - let height: i64 = msg_send![self.sc_display, height]; - Ok(size(px(width as f32), px(height as f32))) - } - } - - fn stream( - &self, - frame_callback: Box, - ) -> oneshot::Receiver>> { - unsafe { - let stream: id = msg_send![class!(SCStream), alloc]; - let filter: id = msg_send![class!(SCContentFilter), alloc]; - let configuration: id = msg_send![class!(SCStreamConfiguration), alloc]; - let delegate: id = msg_send![DELEGATE_CLASS, alloc]; - let output: id = msg_send![OUTPUT_CLASS, alloc]; - - let excluded_windows = NSArray::array(nil); - let filter: id = msg_send![filter, initWithDisplay:self.sc_display excludingWindows:excluded_windows]; - let configuration: id = msg_send![configuration, init]; - let delegate: id = msg_send![delegate, init]; - let output: id = msg_send![output, init]; - - output.as_mut().unwrap().set_ivar( - FRAME_CALLBACK_IVAR, - Box::into_raw(Box::new(frame_callback)) as *mut c_void, - ); - - let stream: id = msg_send![stream, initWithFilter:filter configuration:configuration delegate:delegate]; - - let (mut tx, rx) = oneshot::channel(); - - let mut error: id = nil; - let _: () = msg_send![stream, addStreamOutput:output type:SCStreamOutputTypeScreen sampleHandlerQueue:0 error:&mut error as *mut id]; - if error != nil { - let message: id = msg_send![error, localizedDescription]; - tx.send(Err(anyhow!("failed to add stream output {message:?}"))) - .ok(); - return rx; - } - - let tx = Rc::new(RefCell::new(Some(tx))); - let handler = ConcreteBlock::new({ - move |error: id| { - let result = if error == nil { - let stream = MacScreenCaptureStream { - sc_stream: stream, - sc_stream_output: output, - }; - Ok(Box::new(stream) as Box) - } else { - let message: id = msg_send![error, localizedDescription]; - Err(anyhow!("failed to stop screen capture stream {message:?}")) - }; - if let Some(tx) = tx.borrow_mut().take() { - tx.send(result).ok(); - } - } - }); - let handler = handler.copy(); - let _: () = msg_send![stream, startCaptureWithCompletionHandler:handler]; - rx - } - } -} - -impl Drop for MacScreenCaptureSource { - fn drop(&mut self) { - unsafe { - let _: () = msg_send![self.sc_display, release]; - } - } -} - -impl ScreenCaptureStream for MacScreenCaptureStream {} - -impl Drop for MacScreenCaptureStream { - fn drop(&mut self) { - unsafe { - let mut error: id = nil; - let _: () = msg_send![self.sc_stream, removeStreamOutput:self.sc_stream_output type:SCStreamOutputTypeScreen error:&mut error as *mut _]; - if error != nil { - let message: id = msg_send![error, localizedDescription]; - log::error!("failed to add stream output {message:?}"); - } - - let handler = ConcreteBlock::new(move |error: id| { - if error != nil { - let message: id = msg_send![error, localizedDescription]; - log::error!("failed to stop screen capture stream {message:?}"); - } - }); - let block = handler.copy(); - let _: () = msg_send![self.sc_stream, stopCaptureWithCompletionHandler:block]; - let _: () = msg_send![self.sc_stream, release]; - let _: () = msg_send![self.sc_stream_output, release]; - } - } -} - -pub(crate) fn get_sources() -> oneshot::Receiver>>> { - unsafe { - let (mut tx, rx) = oneshot::channel(); - let tx = Rc::new(RefCell::new(Some(tx))); - - let block = ConcreteBlock::new(move |shareable_content: id, error: id| { - let Some(mut tx) = tx.borrow_mut().take() else { - return; - }; - let result = if error == nil { - let displays: id = msg_send![shareable_content, displays]; - let mut result = Vec::new(); - for i in 0..displays.count() { - let display = displays.objectAtIndex(i); - let source = MacScreenCaptureSource { - sc_display: msg_send![display, retain], - }; - result.push(Box::new(source) as Box); - } - Ok(result) - } else { - let msg: id = msg_send![error, localizedDescription]; - Err(anyhow!("Failed to register: {:?}", msg)) - }; - tx.send(result).ok(); - }); - let block = block.copy(); - - let _: () = msg_send![ - class!(SCShareableContent), - getShareableContentExcludingDesktopWindows:YES - onScreenWindowsOnly:YES - completionHandler:block]; - rx - } -} - -#[ctor] -unsafe fn build_classes() { - let mut decl = ClassDecl::new("GPUIStreamDelegate", class!(NSObject)).unwrap(); - decl.add_method( - sel!(outputVideoEffectDidStartForStream:), - output_video_effect_did_start_for_stream as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(outputVideoEffectDidStopForStream:), - output_video_effect_did_stop_for_stream as extern "C" fn(&Object, Sel, id), - ); - decl.add_method( - sel!(stream:didStopWithError:), - stream_did_stop_with_error as extern "C" fn(&Object, Sel, id, id), - ); - DELEGATE_CLASS = decl.register(); - - let mut decl = ClassDecl::new("GPUIStreamOutput", class!(NSObject)).unwrap(); - decl.add_method( - sel!(stream:didOutputSampleBuffer:ofType:), - stream_did_output_sample_buffer_of_type as extern "C" fn(&Object, Sel, id, id, NSInteger), - ); - decl.add_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR); - - OUTPUT_CLASS = decl.register(); -} - -extern "C" fn output_video_effect_did_start_for_stream(_this: &Object, _: Sel, _stream: id) {} - -extern "C" fn output_video_effect_did_stop_for_stream(_this: &Object, _: Sel, _stream: id) {} - -extern "C" fn stream_did_stop_with_error(_this: &Object, _: Sel, _stream: id, _error: id) {} - -extern "C" fn stream_did_output_sample_buffer_of_type( - this: &Object, - _: Sel, - _stream: id, - sample_buffer: id, - buffer_type: NSInteger, -) { - if buffer_type != SCStreamOutputTypeScreen { - return; - } - - unsafe { - let sample_buffer = sample_buffer as CMSampleBufferRef; - let sample_buffer = CMSampleBuffer::wrap_under_get_rule(sample_buffer); - if let Some(buffer) = sample_buffer.image_buffer() { - let callback: Box> = - Box::from_raw(*this.get_ivar::<*mut c_void>(FRAME_CALLBACK_IVAR) as *mut _); - callback(ScreenCaptureFrame(buffer)); - mem::forget(callback); - } - } -} diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 70462cb5e2..d17739239e 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -7,5 +7,3 @@ pub(crate) use dispatcher::*; pub(crate) use display::*; pub(crate) use platform::*; pub(crate) use window::*; - -pub use platform::TestScreenCaptureSource; diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 67227b60fe..aadbe9b595 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -1,7 +1,7 @@ use crate::{ - px, size, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, - Keymap, Platform, PlatformDisplay, PlatformTextSystem, ScreenCaptureFrame, ScreenCaptureSource, - ScreenCaptureStream, Task, TestDisplay, TestWindow, WindowAppearance, WindowParams, + AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, Keymap, + Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow, WindowAppearance, + WindowParams, }; use anyhow::Result; use collections::VecDeque; @@ -31,7 +31,6 @@ pub(crate) struct TestPlatform { #[cfg(any(target_os = "linux", target_os = "freebsd"))] current_primary_item: Mutex>, pub(crate) prompts: RefCell, - screen_capture_sources: RefCell>, pub opened_url: RefCell>, pub text_system: Arc, #[cfg(target_os = "windows")] @@ -39,31 +38,6 @@ pub(crate) struct TestPlatform { weak: Weak, } -#[derive(Clone)] -/// A fake screen capture source, used for testing. -pub struct TestScreenCaptureSource {} - -pub struct TestScreenCaptureStream {} - -impl ScreenCaptureSource for TestScreenCaptureSource { - fn resolution(&self) -> Result> { - Ok(size(px(1.), px(1.))) - } - - fn stream( - &self, - _frame_callback: Box, - ) -> oneshot::Receiver>> { - let (mut tx, rx) = oneshot::channel(); - let stream = TestScreenCaptureStream {}; - tx.send(Ok(Box::new(stream) as Box)) - .ok(); - rx - } -} - -impl ScreenCaptureStream for TestScreenCaptureStream {} - #[derive(Default)] pub(crate) struct TestPrompts { multiple_choice: VecDeque>, @@ -98,7 +72,6 @@ impl TestPlatform { background_executor: executor, foreground_executor, prompts: Default::default(), - screen_capture_sources: Default::default(), active_cursor: Default::default(), active_display: Rc::new(TestDisplay::new()), active_window: Default::default(), @@ -141,10 +114,6 @@ impl TestPlatform { !self.prompts.borrow().multiple_choice.is_empty() } - pub(crate) fn set_screen_capture_sources(&self, sources: Vec) { - *self.screen_capture_sources.borrow_mut() = sources; - } - pub(crate) fn prompt(&self, msg: &str, detail: Option<&str>) -> oneshot::Receiver { let (tx, rx) = oneshot::channel(); self.background_executor() @@ -233,20 +202,6 @@ impl Platform for TestPlatform { Some(self.active_display.clone()) } - fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>> { - let (mut tx, rx) = oneshot::channel(); - tx.send(Ok(self - .screen_capture_sources - .borrow() - .iter() - .map(|source| Box::new(source.clone()) as Box) - .collect())) - .ok(); - rx - } - fn active_window(&self) -> Option { self.active_window .borrow() @@ -375,13 +330,6 @@ impl Platform for TestPlatform { } } -impl TestScreenCaptureSource { - /// Create a fake screen capture source, for testing. - pub fn new() -> Self { - Self {} - } -} - #[cfg(target_os = "windows")] impl Drop for TestPlatform { fn drop(&mut self) { diff --git a/crates/gpui/src/platform/windows.rs b/crates/gpui/src/platform/windows.rs index 51d09f0013..84cf107c70 100644 --- a/crates/gpui/src/platform/windows.rs +++ b/crates/gpui/src/platform/windows.rs @@ -21,5 +21,3 @@ pub(crate) use window::*; pub(crate) use wrapper::*; pub(crate) use windows::Win32::Foundation::HWND; - -pub(crate) type PlatformScreenCaptureFrame = (); diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index c2bbc9890c..29443afabb 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -325,14 +325,6 @@ impl Platform for WindowsPlatform { WindowsDisplay::primary_monitor().map(|display| Rc::new(display) as Rc) } - fn screen_capture_sources( - &self, - ) -> oneshot::Receiver>>> { - let (mut tx, rx) = oneshot::channel(); - tx.send(Err(anyhow!("screen capture not implemented"))).ok(); - rx - } - fn active_window(&self) -> Option { let active_window_hwnd = unsafe { GetActiveWindow() }; self.try_get_windows_inner_from_hwnd(active_window_hwnd) diff --git a/crates/http_client/Cargo.toml b/crates/http_client/Cargo.toml index a4f10cff18..ac8e254b84 100644 --- a/crates/http_client/Cargo.toml +++ b/crates/http_client/Cargo.toml @@ -20,7 +20,7 @@ bytes.workspace = true anyhow.workspace = true derive_more.workspace = true futures.workspace = true -http.workspace = true +http = "1.1" log.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/live_kit_client/Cargo.toml b/crates/live_kit_client/Cargo.toml index 921c048f23..e23c63453e 100644 --- a/crates/live_kit_client/Cargo.toml +++ b/crates/live_kit_client/Cargo.toml @@ -2,7 +2,7 @@ name = "live_kit_client" version = "0.1.0" edition = "2021" -description = "Logic for using LiveKit with GPUI" +description = "Bindings to LiveKit Swift client SDK" publish = false license = "GPL-3.0-or-later" @@ -19,37 +19,42 @@ name = "test_app" [features] no-webrtc = [] test-support = [ + "async-trait", "collections/test-support", "gpui/test-support", + "live_kit_server", "nanoid", ] [dependencies] anyhow.workspace = true -async-trait.workspace = true -collections.workspace = true -cpal = "0.15" +async-broadcast = "0.7" +async-trait = { workspace = true, optional = true } +collections = { workspace = true, optional = true } futures.workspace = true -gpui.workspace = true -http_2 = { package = "http", version = "0.2.1" } -live_kit_server.workspace = true +gpui = { workspace = true, optional = true } +live_kit_server = { workspace = true, optional = true } log.workspace = true media.workspace = true nanoid = { workspace = true, optional = true} parking_lot.workspace = true postage.workspace = true -util.workspace = true -http_client.workspace = true - -[target.'cfg(not(target_os = "windows"))'.dependencies] -livekit.workspace = true [target.'cfg(target_os = "macos")'.dependencies] core-foundation.workspace = true +[target.'cfg(all(not(target_os = "macos")))'.dependencies] +async-trait = { workspace = true } +collections = { workspace = true } +gpui = { workspace = true } +live_kit_server.workspace = true +nanoid.workspace = true + [dev-dependencies] +async-trait.workspace = true collections = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } +live_kit_server.workspace = true nanoid.workspace = true sha2.workspace = true simplelog.workspace = true diff --git a/crates/live_kit_client/LiveKitBridge/Package.resolved b/crates/live_kit_client/LiveKitBridge/Package.resolved new file mode 100644 index 0000000000..b925bc8f0d --- /dev/null +++ b/crates/live_kit_client/LiveKitBridge/Package.resolved @@ -0,0 +1,52 @@ +{ + "object": { + "pins": [ + { + "package": "LiveKit", + "repositoryURL": "https://github.com/livekit/client-sdk-swift.git", + "state": { + "branch": null, + "revision": "7331b813a5ab8a95cfb81fb2b4ed10519428b9ff", + "version": "1.0.12" + } + }, + { + "package": "Promises", + "repositoryURL": "https://github.com/google/promises.git", + "state": { + "branch": null, + "revision": "ec957ccddbcc710ccc64c9dcbd4c7006fcf8b73a", + "version": "2.2.0" + } + }, + { + "package": "WebRTC", + "repositoryURL": "https://github.com/webrtc-sdk/Specs.git", + "state": { + "branch": null, + "revision": "2f6bab30c8df0fe59ab3e58bc99097f757f85f65", + "version": "104.5112.17" + } + }, + { + "package": "swift-log", + "repositoryURL": "https://github.com/apple/swift-log.git", + "state": { + "branch": null, + "revision": "32e8d724467f8fe623624570367e3d50c5638e46", + "version": "1.5.2" + } + }, + { + "package": "SwiftProtobuf", + "repositoryURL": "https://github.com/apple/swift-protobuf.git", + "state": { + "branch": null, + "revision": "ce20dc083ee485524b802669890291c0d8090170", + "version": "1.22.1" + } + } + ] + }, + "version": 1 +} diff --git a/crates/live_kit_client/LiveKitBridge/Package.swift b/crates/live_kit_client/LiveKitBridge/Package.swift new file mode 100644 index 0000000000..d7b5c271b9 --- /dev/null +++ b/crates/live_kit_client/LiveKitBridge/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 5.5 + +import PackageDescription + +let package = Package( + name: "LiveKitBridge", + platforms: [ + .macOS(.v10_15) + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "LiveKitBridge", + type: .static, + targets: ["LiveKitBridge"]), + ], + dependencies: [ + .package(url: "https://github.com/livekit/client-sdk-swift.git", .exact("1.0.12")), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "LiveKitBridge", + dependencies: [.product(name: "LiveKit", package: "client-sdk-swift")]), + ] +) diff --git a/crates/live_kit_client/LiveKitBridge/README.md b/crates/live_kit_client/LiveKitBridge/README.md new file mode 100644 index 0000000000..b982c67286 --- /dev/null +++ b/crates/live_kit_client/LiveKitBridge/README.md @@ -0,0 +1,3 @@ +# LiveKitBridge + +A description of this package. diff --git a/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift new file mode 100644 index 0000000000..7468c08791 --- /dev/null +++ b/crates/live_kit_client/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift @@ -0,0 +1,383 @@ +import Foundation +import LiveKit +import WebRTC +import ScreenCaptureKit + +class LKRoomDelegate: RoomDelegate { + var data: UnsafeRawPointer + var onDidDisconnect: @convention(c) (UnsafeRawPointer) -> Void + var onDidSubscribeToRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void + var onDidUnsubscribeFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void + var onMuteChangedFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void + var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void + var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void + var onDidUnsubscribeFromRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void + var onDidPublishOrUnpublishLocalAudioTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void + var onDidPublishOrUnpublishLocalVideoTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void + + init( + data: UnsafeRawPointer, + onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void, + onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void, + onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, + onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, + onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void, + onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, + onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, + onDidPublishOrUnpublishLocalAudioTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void, + onDidPublishOrUnpublishLocalVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void + ) + { + self.data = data + self.onDidDisconnect = onDidDisconnect + self.onDidSubscribeToRemoteAudioTrack = onDidSubscribeToRemoteAudioTrack + self.onDidUnsubscribeFromRemoteAudioTrack = onDidUnsubscribeFromRemoteAudioTrack + self.onDidSubscribeToRemoteVideoTrack = onDidSubscribeToRemoteVideoTrack + self.onDidUnsubscribeFromRemoteVideoTrack = onDidUnsubscribeFromRemoteVideoTrack + self.onMuteChangedFromRemoteAudioTrack = onMuteChangedFromRemoteAudioTrack + self.onActiveSpeakersChanged = onActiveSpeakersChanged + self.onDidPublishOrUnpublishLocalAudioTrack = onDidPublishOrUnpublishLocalAudioTrack + self.onDidPublishOrUnpublishLocalVideoTrack = onDidPublishOrUnpublishLocalVideoTrack + } + + func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) { + if connectionState.isDisconnected { + self.onDidDisconnect(self.data) + } + } + + func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) { + if track.kind == .video { + self.onDidSubscribeToRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque()) + } else if track.kind == .audio { + self.onDidSubscribeToRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque(), Unmanaged.passUnretained(publication).toOpaque()) + } + } + + func room(_ room: Room, participant: Participant, didUpdate publication: TrackPublication, muted: Bool) { + if publication.kind == .audio { + self.onMuteChangedFromRemoteAudioTrack(self.data, publication.sid as CFString, muted) + } + } + + func room(_ room: Room, didUpdate speakers: [Participant]) { + guard let speaker_ids = speakers.compactMap({ $0.identity as CFString }) as CFArray? else { return } + self.onActiveSpeakersChanged(self.data, speaker_ids) + } + + func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) { + if track.kind == .video { + self.onDidUnsubscribeFromRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString) + } else if track.kind == .audio { + self.onDidUnsubscribeFromRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString) + } + } + + func room(_ room: Room, localParticipant: LocalParticipant, didPublish publication: LocalTrackPublication) { + if publication.kind == .video { + self.onDidPublishOrUnpublishLocalVideoTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), true) + } else if publication.kind == .audio { + self.onDidPublishOrUnpublishLocalAudioTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), true) + } + } + + func room(_ room: Room, localParticipant: LocalParticipant, didUnpublish publication: LocalTrackPublication) { + if publication.kind == .video { + self.onDidPublishOrUnpublishLocalVideoTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), false) + } else if publication.kind == .audio { + self.onDidPublishOrUnpublishLocalAudioTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), false) + } + } +} + +class LKVideoRenderer: NSObject, VideoRenderer { + var data: UnsafeRawPointer + var onFrame: @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool + var onDrop: @convention(c) (UnsafeRawPointer) -> Void + var adaptiveStreamIsEnabled: Bool = false + var adaptiveStreamSize: CGSize = .zero + weak var track: VideoTrack? + + init(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) { + self.data = data + self.onFrame = onFrame + self.onDrop = onDrop + } + + deinit { + self.onDrop(self.data) + } + + func setSize(_ size: CGSize) { + } + + func renderFrame(_ frame: RTCVideoFrame?) { + let buffer = frame?.buffer as? RTCCVPixelBuffer + if let pixelBuffer = buffer?.pixelBuffer { + if !self.onFrame(self.data, pixelBuffer) { + DispatchQueue.main.async { + self.track?.remove(videoRenderer: self) + } + } + } + } +} + +@_cdecl("LKRoomDelegateCreate") +public func LKRoomDelegateCreate( + data: UnsafeRawPointer, + onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void, + onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void, + onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, + onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, + onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void, + onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, + onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, + onDidPublishOrUnpublishLocalAudioTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void, + onDidPublishOrUnpublishLocalVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void +) -> UnsafeMutableRawPointer { + let delegate = LKRoomDelegate( + data: data, + onDidDisconnect: onDidDisconnect, + onDidSubscribeToRemoteAudioTrack: onDidSubscribeToRemoteAudioTrack, + onDidUnsubscribeFromRemoteAudioTrack: onDidUnsubscribeFromRemoteAudioTrack, + onMuteChangedFromRemoteAudioTrack: onMuteChangedFromRemoteAudioTrack, + onActiveSpeakersChanged: onActiveSpeakerChanged, + onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack, + onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack, + onDidPublishOrUnpublishLocalAudioTrack: onDidPublishOrUnpublishLocalAudioTrack, + onDidPublishOrUnpublishLocalVideoTrack: onDidPublishOrUnpublishLocalVideoTrack + ) + return Unmanaged.passRetained(delegate).toOpaque() +} + +@_cdecl("LKRoomCreate") +public func LKRoomCreate(delegate: UnsafeRawPointer) -> UnsafeMutableRawPointer { + let delegate = Unmanaged.fromOpaque(delegate).takeUnretainedValue() + return Unmanaged.passRetained(Room(delegate: delegate)).toOpaque() +} + +@_cdecl("LKRoomConnect") +public func LKRoomConnect(room: UnsafeRawPointer, url: CFString, token: CFString, callback: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, callback_data: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + + room.connect(url as String, token as String).then { _ in + callback(callback_data, UnsafeRawPointer(nil) as! CFString?) + }.catch { error in + callback(callback_data, error.localizedDescription as CFString) + } +} + +@_cdecl("LKRoomDisconnect") +public func LKRoomDisconnect(room: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + room.disconnect() +} + +@_cdecl("LKRoomPublishVideoTrack") +public func LKRoomPublishVideoTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, UnsafeMutableRawPointer?, CFString?) -> Void, callback_data: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + room.localParticipant?.publishVideoTrack(track: track).then { publication in + callback(callback_data, Unmanaged.passRetained(publication).toOpaque(), nil) + }.catch { error in + callback(callback_data, nil, error.localizedDescription as CFString) + } +} + +@_cdecl("LKRoomPublishAudioTrack") +public func LKRoomPublishAudioTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, UnsafeMutableRawPointer?, CFString?) -> Void, callback_data: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + room.localParticipant?.publishAudioTrack(track: track).then { publication in + callback(callback_data, Unmanaged.passRetained(publication).toOpaque(), nil) + }.catch { error in + callback(callback_data, nil, error.localizedDescription as CFString) + } +} + + +@_cdecl("LKRoomUnpublishTrack") +public func LKRoomUnpublishTrack(room: UnsafeRawPointer, publication: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + let _ = room.localParticipant?.unpublish(publication: publication) +} + +@_cdecl("LKRoomAudioTracksForRemoteParticipant") +public func LKRoomAudioTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + + for (_, participant) in room.remoteParticipants { + if participant.identity == participantId as String { + return participant.audioTracks.compactMap { $0.track as? RemoteAudioTrack } as CFArray? + } + } + + return nil; +} + +@_cdecl("LKRoomAudioTrackPublicationsForRemoteParticipant") +public func LKRoomAudioTrackPublicationsForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + + for (_, participant) in room.remoteParticipants { + if participant.identity == participantId as String { + return participant.audioTracks.compactMap { $0 as? RemoteTrackPublication } as CFArray? + } + } + + return nil; +} + +@_cdecl("LKRoomVideoTracksForRemoteParticipant") +public func LKRoomVideoTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + + for (_, participant) in room.remoteParticipants { + if participant.identity == participantId as String { + return participant.videoTracks.compactMap { $0.track as? RemoteVideoTrack } as CFArray? + } + } + + return nil; +} + +@_cdecl("LKLocalAudioTrackCreateTrack") +public func LKLocalAudioTrackCreateTrack() -> UnsafeMutableRawPointer { + let track = LocalAudioTrack.createTrack(options: AudioCaptureOptions( + echoCancellation: true, + noiseSuppression: true + )) + + return Unmanaged.passRetained(track).toOpaque() +} + + +@_cdecl("LKCreateScreenShareTrackForDisplay") +public func LKCreateScreenShareTrackForDisplay(display: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + let display = Unmanaged.fromOpaque(display).takeUnretainedValue() + let track = LocalVideoTrack.createMacOSScreenShareTrack(source: display, preferredMethod: .legacy) + return Unmanaged.passRetained(track).toOpaque() +} + +@_cdecl("LKVideoRendererCreate") +public func LKVideoRendererCreate(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer { + Unmanaged.passRetained(LKVideoRenderer(data: data, onFrame: onFrame, onDrop: onDrop)).toOpaque() +} + +@_cdecl("LKVideoTrackAddRenderer") +public func LKVideoTrackAddRenderer(track: UnsafeRawPointer, renderer: UnsafeRawPointer) { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() as! VideoTrack + let renderer = Unmanaged.fromOpaque(renderer).takeRetainedValue() + renderer.track = track + track.add(videoRenderer: renderer) +} + +@_cdecl("LKRemoteVideoTrackGetSid") +public func LKRemoteVideoTrackGetSid(track: UnsafeRawPointer) -> CFString { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + return track.sid! as CFString +} + +@_cdecl("LKRemoteAudioTrackGetSid") +public func LKRemoteAudioTrackGetSid(track: UnsafeRawPointer) -> CFString { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + return track.sid! as CFString +} + +@_cdecl("LKRemoteAudioTrackStart") +public func LKRemoteAudioTrackStart(track: UnsafeRawPointer) { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + track.start() +} + +@_cdecl("LKRemoteAudioTrackStop") +public func LKRemoteAudioTrackStop(track: UnsafeRawPointer) { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + track.stop() +} + +@_cdecl("LKDisplaySources") +public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFArray?, CFString?) -> Void) { + MacOSScreenCapturer.sources(for: .display, includeCurrentApplication: false, preferredMethod: .legacy).then { displaySources in + callback(data, displaySources as CFArray, nil) + }.catch { error in + callback(data, nil, error.localizedDescription as CFString) + } +} + +@_cdecl("LKLocalTrackPublicationSetMute") +public func LKLocalTrackPublicationSetMute( + publication: UnsafeRawPointer, + muted: Bool, + on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, + callback_data: UnsafeRawPointer +) { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + if muted { + publication.mute().then { + on_complete(callback_data, nil) + }.catch { error in + on_complete(callback_data, error.localizedDescription as CFString) + } + } else { + publication.unmute().then { + on_complete(callback_data, nil) + }.catch { error in + on_complete(callback_data, error.localizedDescription as CFString) + } + } +} + +@_cdecl("LKLocalTrackPublicationIsMuted") +public func LKLocalTrackPublicationIsMuted( + publication: UnsafeRawPointer +) -> Bool { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + return publication.muted +} + +@_cdecl("LKRemoteTrackPublicationSetEnabled") +public func LKRemoteTrackPublicationSetEnabled( + publication: UnsafeRawPointer, + enabled: Bool, + on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, + callback_data: UnsafeRawPointer +) { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + publication.set(enabled: enabled).then { + on_complete(callback_data, nil) + }.catch { error in + on_complete(callback_data, error.localizedDescription as CFString) + } +} + +@_cdecl("LKRemoteTrackPublicationIsMuted") +public func LKRemoteTrackPublicationIsMuted( + publication: UnsafeRawPointer +) -> Bool { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.muted +} + +@_cdecl("LKRemoteTrackPublicationGetSid") +public func LKRemoteTrackPublicationGetSid( + publication: UnsafeRawPointer +) -> CFString { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.sid as CFString +} + +@_cdecl("LKLocalTrackPublicationGetSid") +public func LKLocalTrackPublicationGetSid( + publication: UnsafeRawPointer +) -> CFString { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.sid as CFString +} diff --git a/crates/live_kit_client/build.rs b/crates/live_kit_client/build.rs new file mode 100644 index 0000000000..2fdfd982bf --- /dev/null +++ b/crates/live_kit_client/build.rs @@ -0,0 +1,185 @@ +use serde::Deserialize; +use std::{ + env, + path::{Path, PathBuf}, + process::Command, +}; + +const SWIFT_PACKAGE_NAME: &str = "LiveKitBridge"; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SwiftTargetInfo { + pub triple: String, + pub unversioned_triple: String, + pub module_triple: String, + pub swift_runtime_compatibility_version: String, + #[serde(rename = "librariesRequireRPath")] + pub libraries_require_rpath: bool, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SwiftPaths { + pub runtime_library_paths: Vec, + pub runtime_library_import_paths: Vec, + pub runtime_resource_path: String, +} + +#[derive(Debug, Deserialize)] +pub struct SwiftTarget { + pub target: SwiftTargetInfo, + pub paths: SwiftPaths, +} + +const MACOS_TARGET_VERSION: &str = "10.15.7"; + +fn main() { + if cfg!(all( + target_os = "macos", + not(any(test, feature = "test-support", feature = "no-webrtc")), + )) { + let swift_target = get_swift_target(); + + build_bridge(&swift_target); + link_swift_stdlib(&swift_target); + link_webrtc_framework(&swift_target); + + // Register exported Objective-C selectors, protocols, etc when building example binaries. + println!("cargo:rustc-link-arg=-Wl,-ObjC"); + } +} + +fn build_bridge(swift_target: &SwiftTarget) { + println!("cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET"); + println!("cargo:rerun-if-changed={}/Sources", SWIFT_PACKAGE_NAME); + println!( + "cargo:rerun-if-changed={}/Package.swift", + SWIFT_PACKAGE_NAME + ); + println!( + "cargo:rerun-if-changed={}/Package.resolved", + SWIFT_PACKAGE_NAME + ); + + let swift_package_root = swift_package_root(); + let swift_target_folder = swift_target_folder(); + let swift_cache_folder = swift_cache_folder(); + if !Command::new("swift") + .arg("build") + .arg("--disable-automatic-resolution") + .args(["--configuration", &env::var("PROFILE").unwrap()]) + .args(["--triple", &swift_target.target.triple]) + .args(["--build-path".into(), swift_target_folder]) + .args(["--cache-path".into(), swift_cache_folder]) + .current_dir(&swift_package_root) + .status() + .unwrap() + .success() + { + panic!( + "Failed to compile swift package in {}", + swift_package_root.display() + ); + } + + println!( + "cargo:rustc-link-search=native={}", + swift_target.out_dir_path().display() + ); + println!("cargo:rustc-link-lib=static={}", SWIFT_PACKAGE_NAME); +} + +fn link_swift_stdlib(swift_target: &SwiftTarget) { + for path in &swift_target.paths.runtime_library_paths { + println!("cargo:rustc-link-search=native={}", path); + } +} + +fn link_webrtc_framework(swift_target: &SwiftTarget) { + let swift_out_dir_path = swift_target.out_dir_path(); + println!("cargo:rustc-link-lib=framework=WebRTC"); + println!( + "cargo:rustc-link-search=framework={}", + swift_out_dir_path.display() + ); + // Find WebRTC.framework as a sibling of the executable when running tests. + println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path"); + // Find WebRTC.framework in parent directory of the executable when running examples. + println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/.."); + + let source_path = swift_out_dir_path.join("WebRTC.framework"); + let deps_dir_path = + PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../../deps/WebRTC.framework"); + let target_dir_path = + PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../../WebRTC.framework"); + copy_dir(&source_path, &deps_dir_path); + copy_dir(&source_path, &target_dir_path); +} + +fn get_swift_target() -> SwiftTarget { + let mut arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + if arch == "aarch64" { + arch = "arm64".into(); + } + let target = format!("{}-apple-macosx{}", arch, MACOS_TARGET_VERSION); + + let swift_target_info_str = Command::new("swift") + .args(["-target", &target, "-print-target-info"]) + .output() + .unwrap() + .stdout; + + serde_json::from_slice(&swift_target_info_str).unwrap() +} + +fn swift_package_root() -> PathBuf { + env::current_dir().unwrap().join(SWIFT_PACKAGE_NAME) +} + +fn swift_target_folder() -> PathBuf { + let target = env::var("TARGET").unwrap(); + env::current_dir() + .unwrap() + .join(format!("../../target/{target}/{SWIFT_PACKAGE_NAME}_target")) +} + +fn swift_cache_folder() -> PathBuf { + let target = env::var("TARGET").unwrap(); + env::current_dir() + .unwrap() + .join(format!("../../target/{target}/{SWIFT_PACKAGE_NAME}_cache")) +} + +fn copy_dir(source: &Path, destination: &Path) { + assert!( + Command::new("rm") + .arg("-rf") + .arg(destination) + .status() + .unwrap() + .success(), + "could not remove {:?} before copying", + destination + ); + + assert!( + Command::new("cp") + .arg("-R") + .args([source, destination]) + .status() + .unwrap() + .success(), + "could not copy {:?} to {:?}", + source, + destination + ); +} + +impl SwiftTarget { + fn out_dir_path(&self) -> PathBuf { + swift_target_folder() + .join(&self.target.unversioned_triple) + .join(env::var("PROFILE").unwrap()) + } +} diff --git a/crates/live_kit_client/examples/test_app.rs b/crates/live_kit_client/examples/test_app.rs index 8edd171d84..de8be97e86 100644 --- a/crates/live_kit_client/examples/test_app.rs +++ b/crates/live_kit_client/examples/test_app.rs @@ -1,53 +1,18 @@ -#![cfg_attr(windows, allow(unused))] - -use gpui::{ - actions, bounds, div, point, - prelude::{FluentBuilder as _, IntoElement}, - px, rgb, size, AsyncAppContext, Bounds, InteractiveElement, KeyBinding, Menu, MenuItem, - ParentElement, Pixels, Render, ScreenCaptureStream, SharedString, - StatefulInteractiveElement as _, Styled, Task, View, ViewContext, VisualContext, WindowBounds, - WindowHandle, WindowOptions, -}; -#[cfg(not(target_os = "windows"))] -use live_kit_client::{ - capture_local_audio_track, capture_local_video_track, - id::ParticipantIdentity, - options::{TrackPublishOptions, VideoCodec}, - participant::{Participant, RemoteParticipant}, - play_remote_audio_track, - publication::{LocalTrackPublication, RemoteTrackPublication}, - track::{LocalTrack, RemoteTrack, RemoteVideoTrack, TrackSource}, - AudioStream, RemoteVideoTrackView, Room, RoomEvent, RoomOptions, -}; - -#[cfg(target_os = "windows")] -use live_kit_client::{ - participant::{Participant, RemoteParticipant}, - publication::{LocalTrackPublication, RemoteTrackPublication}, - track::{LocalTrack, RemoteTrack, RemoteVideoTrack}, - AudioStream, RemoteVideoTrackView, Room, RoomEvent, -}; +use std::time::Duration; +use futures::StreamExt; +use gpui::{actions, KeyBinding, Menu, MenuItem}; +use live_kit_client::{LocalAudioTrack, LocalVideoTrack, Room, RoomUpdate}; use live_kit_server::token::{self, VideoGrant}; use log::LevelFilter; -use postage::stream::Stream as _; use simplelog::SimpleLogger; actions!(live_kit_client, [Quit]); -#[cfg(windows)] -fn main() {} - -#[cfg(not(windows))] fn main() { SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); gpui::App::new().run(|cx| { - live_kit_client::init( - cx.background_executor().dispatcher.clone(), - cx.http_client(), - ); - #[cfg(any(test, feature = "test-support"))] println!("USING TEST LIVEKIT"); @@ -55,8 +20,10 @@ fn main() { println!("USING REAL LIVEKIT"); cx.activate(true); + cx.on_action(quit); cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]); + cx.set_menus(vec![Menu { name: "Zed".into(), items: vec![MenuItem::Action { @@ -69,26 +36,132 @@ fn main() { let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into()); let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into()); let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into()); - let height = px(800.); - let width = px(800.); cx.spawn(|cx| async move { - let mut windows = Vec::new(); - for i in 0..3 { - let token = token::create( - &live_kit_key, - &live_kit_secret, - Some(&format!("test-participant-{i}")), - VideoGrant::to_join("test-room"), - ) - .unwrap(); + let user_a_token = token::create( + &live_kit_key, + &live_kit_secret, + Some("test-participant-1"), + VideoGrant::to_join("test-room"), + ) + .unwrap(); + let room_a = Room::new(); + room_a.connect(&live_kit_url, &user_a_token).await.unwrap(); - let bounds = bounds(point(width * i, px(0.0)), size(width, height)); - let window = - LivekitWindow::new(live_kit_url.as_str(), token.as_str(), bounds, cx.clone()) - .await; - windows.push(window); + let user2_token = token::create( + &live_kit_key, + &live_kit_secret, + Some("test-participant-2"), + VideoGrant::to_join("test-room"), + ) + .unwrap(); + let room_b = Room::new(); + room_b.connect(&live_kit_url, &user2_token).await.unwrap(); + + let mut room_updates = room_b.updates(); + let audio_track = LocalAudioTrack::create(); + let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap(); + + if let RoomUpdate::SubscribedToRemoteAudioTrack(track, _) = + room_updates.next().await.unwrap() + { + let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + assert_eq!(remote_tracks.len(), 1); + assert_eq!(remote_tracks[0].publisher_id(), "test-participant-1"); + assert_eq!(track.publisher_id(), "test-participant-1"); + } else { + panic!("unexpected message"); } + + audio_track_publication.set_mute(true).await.unwrap(); + + println!("waiting for mute changed!"); + if let RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } = + room_updates.next().await.unwrap() + { + let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + assert_eq!(remote_tracks[0].sid(), track_id); + assert!(muted); + } else { + panic!("unexpected message"); + } + + audio_track_publication.set_mute(false).await.unwrap(); + + if let RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } = + room_updates.next().await.unwrap() + { + let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + assert_eq!(remote_tracks[0].sid(), track_id); + assert!(!muted); + } else { + panic!("unexpected message"); + } + + println!("Pausing for 5 seconds to test audio, make some noise!"); + let timer = cx.background_executor().timer(Duration::from_secs(5)); + timer.await; + let remote_audio_track = room_b + .remote_audio_tracks("test-participant-1") + .pop() + .unwrap(); + room_a.unpublish_track(audio_track_publication); + + // Clear out any active speakers changed messages + let mut next = room_updates.next().await.unwrap(); + while let RoomUpdate::ActiveSpeakersChanged { speakers } = next { + println!("Speakers changed: {:?}", speakers); + next = room_updates.next().await.unwrap(); + } + + if let RoomUpdate::UnsubscribedFromRemoteAudioTrack { + publisher_id, + track_id, + } = next + { + assert_eq!(publisher_id, "test-participant-1"); + assert_eq!(remote_audio_track.sid(), track_id); + assert_eq!(room_b.remote_audio_tracks("test-participant-1").len(), 0); + } else { + panic!("unexpected message"); + } + + let displays = room_a.display_sources().await.unwrap(); + let display = displays.into_iter().next().unwrap(); + + let local_video_track = LocalVideoTrack::screen_share_for_display(&display); + let local_video_track_publication = + room_a.publish_video_track(local_video_track).await.unwrap(); + + if let RoomUpdate::SubscribedToRemoteVideoTrack(track) = + room_updates.next().await.unwrap() + { + let remote_video_tracks = room_b.remote_video_tracks("test-participant-1"); + assert_eq!(remote_video_tracks.len(), 1); + assert_eq!(remote_video_tracks[0].publisher_id(), "test-participant-1"); + assert_eq!(track.publisher_id(), "test-participant-1"); + } else { + panic!("unexpected message"); + } + + let remote_video_track = room_b + .remote_video_tracks("test-participant-1") + .pop() + .unwrap(); + room_a.unpublish_track(local_video_track_publication); + if let RoomUpdate::UnsubscribedFromRemoteVideoTrack { + publisher_id, + track_id, + } = room_updates.next().await.unwrap() + { + assert_eq!(publisher_id, "test-participant-1"); + assert_eq!(remote_video_track.sid(), track_id); + assert_eq!(room_b.remote_video_tracks("test-participant-1").len(), 0); + } else { + panic!("unexpected message"); + } + + cx.update(|cx| cx.shutdown()).ok(); }) .detach(); }); @@ -97,340 +170,3 @@ fn main() { fn quit(_: &Quit, cx: &mut gpui::AppContext) { cx.quit(); } - -struct LivekitWindow { - room: Room, - microphone_track: Option, - screen_share_track: Option, - microphone_stream: Option, - screen_share_stream: Option>, - #[cfg(not(target_os = "windows"))] - remote_participants: Vec<(ParticipantIdentity, ParticipantState)>, - _events_task: Task<()>, -} - -#[derive(Default)] -struct ParticipantState { - audio_output_stream: Option<(RemoteTrackPublication, AudioStream)>, - muted: bool, - screen_share_output_view: Option<(RemoteVideoTrack, View)>, - speaking: bool, -} - -#[cfg(not(windows))] -impl LivekitWindow { - async fn new( - url: &str, - token: &str, - bounds: Bounds, - cx: AsyncAppContext, - ) -> WindowHandle { - let (room, mut events) = Room::connect(url, token, RoomOptions::default()) - .await - .unwrap(); - - cx.update(|cx| { - cx.open_window( - WindowOptions { - window_bounds: Some(WindowBounds::Windowed(bounds)), - ..Default::default() - }, - |cx| { - cx.new_view(|cx| { - let _events_task = cx.spawn(|this, mut cx| async move { - while let Some(event) = events.recv().await { - this.update(&mut cx, |this: &mut LivekitWindow, cx| { - this.handle_room_event(event, cx) - }) - .ok(); - } - }); - - Self { - room, - microphone_track: None, - microphone_stream: None, - screen_share_track: None, - screen_share_stream: None, - remote_participants: Vec::new(), - _events_task, - } - }) - }, - ) - .unwrap() - }) - .unwrap() - } - - fn handle_room_event(&mut self, event: RoomEvent, cx: &mut ViewContext) { - eprintln!("event: {event:?}"); - - match event { - RoomEvent::TrackUnpublished { - publication, - participant, - } => { - let output = self.remote_participant(participant); - let unpublish_sid = publication.sid(); - if output - .audio_output_stream - .as_ref() - .map_or(false, |(track, _)| track.sid() == unpublish_sid) - { - output.audio_output_stream.take(); - } - if output - .screen_share_output_view - .as_ref() - .map_or(false, |(track, _)| track.sid() == unpublish_sid) - { - output.screen_share_output_view.take(); - } - cx.notify(); - } - - RoomEvent::TrackSubscribed { - publication, - participant, - track, - } => { - let output = self.remote_participant(participant); - match track { - RemoteTrack::Audio(track) => { - output.audio_output_stream = - Some((publication.clone(), play_remote_audio_track(&track, cx))); - } - RemoteTrack::Video(track) => { - output.screen_share_output_view = Some(( - track.clone(), - cx.new_view(|cx| RemoteVideoTrackView::new(track, cx)), - )); - } - } - cx.notify(); - } - - RoomEvent::TrackMuted { participant, .. } => { - if let Participant::Remote(participant) = participant { - self.remote_participant(participant).muted = true; - cx.notify(); - } - } - - RoomEvent::TrackUnmuted { participant, .. } => { - if let Participant::Remote(participant) = participant { - self.remote_participant(participant).muted = false; - cx.notify(); - } - } - - RoomEvent::ActiveSpeakersChanged { speakers } => { - for (identity, output) in &mut self.remote_participants { - output.speaking = speakers.iter().any(|speaker| { - if let Participant::Remote(speaker) = speaker { - speaker.identity() == *identity - } else { - false - } - }); - } - cx.notify(); - } - - _ => {} - } - - cx.notify(); - } - - fn remote_participant(&mut self, participant: RemoteParticipant) -> &mut ParticipantState { - match self - .remote_participants - .binary_search_by_key(&&participant.identity(), |row| &row.0) - { - Ok(ix) => &mut self.remote_participants[ix].1, - Err(ix) => { - self.remote_participants - .insert(ix, (participant.identity(), ParticipantState::default())); - &mut self.remote_participants[ix].1 - } - } - } - - fn toggle_mute(&mut self, cx: &mut ViewContext) { - if let Some(track) = &self.microphone_track { - if track.is_muted() { - track.unmute(); - } else { - track.mute(); - } - cx.notify(); - } else { - let participant = self.room.local_participant(); - cx.spawn(|this, mut cx| async move { - let (track, stream) = cx.update(|cx| capture_local_audio_track(cx))??; - let publication = participant - .publish_track( - LocalTrack::Audio(track), - TrackPublishOptions { - source: TrackSource::Microphone, - ..Default::default() - }, - ) - .await - .unwrap(); - this.update(&mut cx, |this, cx| { - this.microphone_track = Some(publication); - this.microphone_stream = Some(stream); - cx.notify(); - }) - }) - .detach(); - } - } - - fn toggle_screen_share(&mut self, cx: &mut ViewContext) { - if let Some(track) = self.screen_share_track.take() { - self.screen_share_stream.take(); - let participant = self.room.local_participant(); - cx.background_executor() - .spawn(async move { - participant.unpublish_track(&track.sid()).await.unwrap(); - }) - .detach(); - cx.notify(); - } else { - let participant = self.room.local_participant(); - let sources = cx.screen_capture_sources(); - cx.spawn(|this, mut cx| async move { - let sources = sources.await.unwrap()?; - let source = sources.into_iter().next().unwrap(); - let (track, stream) = capture_local_video_track(&*source).await?; - let publication = participant - .publish_track( - LocalTrack::Video(track), - TrackPublishOptions { - source: TrackSource::Screenshare, - video_codec: VideoCodec::H264, - ..Default::default() - }, - ) - .await - .unwrap(); - this.update(&mut cx, |this, cx| { - this.screen_share_track = Some(publication); - this.screen_share_stream = Some(stream); - cx.notify(); - }) - }) - .detach(); - } - } - - fn toggle_remote_audio_for_participant( - &mut self, - identity: &ParticipantIdentity, - cx: &mut ViewContext, - ) -> Option<()> { - let participant = self.remote_participants.iter().find_map(|(id, state)| { - if id == identity { - Some(state) - } else { - None - } - })?; - let publication = &participant.audio_output_stream.as_ref()?.0; - publication.set_enabled(!publication.is_enabled()); - cx.notify(); - Some(()) - } -} - -#[cfg(not(windows))] -impl Render for LivekitWindow { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - fn button() -> gpui::Div { - div() - .w(px(180.0)) - .h(px(30.0)) - .px_2() - .m_2() - .bg(rgb(0x8888ff)) - } - - div() - .bg(rgb(0xffffff)) - .size_full() - .flex() - .flex_col() - .child( - div().bg(rgb(0xffd4a8)).flex().flex_row().children([ - button() - .id("toggle-mute") - .child(if let Some(track) = &self.microphone_track { - if track.is_muted() { - "Unmute" - } else { - "Mute" - } - } else { - "Publish mic" - }) - .on_click(cx.listener(|this, _, cx| this.toggle_mute(cx))), - button() - .id("toggle-screen-share") - .child(if self.screen_share_track.is_none() { - "Share screen" - } else { - "Unshare screen" - }) - .on_click(cx.listener(|this, _, cx| this.toggle_screen_share(cx))), - ]), - ) - .child( - div() - .id("remote-participants") - .overflow_y_scroll() - .flex() - .flex_col() - .flex_grow() - .children(self.remote_participants.iter().map(|(identity, state)| { - div() - .h(px(300.0)) - .flex() - .flex_col() - .m_2() - .px_2() - .bg(rgb(0x8888ff)) - .child(SharedString::from(if state.speaking { - format!("{} (speaking)", &identity.0) - } else if state.muted { - format!("{} (muted)", &identity.0) - } else { - identity.0.clone() - })) - .when_some(state.audio_output_stream.as_ref(), |el, state| { - el.child( - button() - .id(SharedString::from(identity.0.clone())) - .child(if state.0.is_enabled() { - "Deafen" - } else { - "Undeafen" - }) - .on_click(cx.listener({ - let identity = identity.clone(); - move |this, _, cx| { - this.toggle_remote_audio_for_participant( - &identity, cx, - ); - } - })), - ) - }) - .children(state.screen_share_output_view.as_ref().map(|e| e.1.clone())) - })), - ) - } -} diff --git a/crates/live_kit_client/src/live_kit_client.rs b/crates/live_kit_client/src/live_kit_client.rs index ad2e72d67f..4820a4eedb 100644 --- a/crates/live_kit_client/src/live_kit_client.rs +++ b/crates/live_kit_client/src/live_kit_client.rs @@ -1,387 +1,37 @@ -#![cfg_attr(target_os = "windows", allow(unused))] +#![allow(clippy::arc_with_non_send_sync)] -mod remote_video_track_view; -#[cfg(any(test, feature = "test-support", target_os = "windows"))] +use std::sync::Arc; + +#[cfg(all(target_os = "macos", not(any(test, feature = "test-support"))))] +pub mod prod; + +#[cfg(all(target_os = "macos", not(any(test, feature = "test-support"))))] +pub use prod::*; + +#[cfg(any(test, feature = "test-support", not(target_os = "macos")))] pub mod test; -use anyhow::{anyhow, Context as _, Result}; -use cpal::{ - traits::{DeviceTrait, HostTrait, StreamTrait as _}, - StreamConfig, -}; -use futures::{io, Stream, StreamExt as _}; -use gpui::{AppContext, ScreenCaptureFrame, ScreenCaptureSource, ScreenCaptureStream, Task}; -use parking_lot::Mutex; -use std::{borrow::Cow, future::Future, pin::Pin, sync::Arc}; -use util::{debug_panic, ResultExt as _, TryFutureExt}; -#[cfg(not(target_os = "windows"))] -use webrtc::{ - audio_frame::AudioFrame, - audio_source::{native::NativeAudioSource, AudioSourceOptions, RtcAudioSource}, - audio_stream::native::NativeAudioStream, - video_frame::{VideoBuffer, VideoFrame, VideoRotation}, - video_source::{native::NativeVideoSource, RtcVideoSource, VideoResolution}, - video_stream::native::NativeVideoStream, -}; - -#[cfg(all(not(any(test, feature = "test-support")), not(target_os = "windows")))] -pub use livekit::*; -#[cfg(any(test, feature = "test-support", target_os = "windows"))] +#[cfg(any(test, feature = "test-support", not(target_os = "macos")))] pub use test::*; -pub use remote_video_track_view::{RemoteVideoTrackView, RemoteVideoTrackViewEvent}; +pub type Sid = String; -pub struct AudioStream { - _tasks: [Task>; 2], +#[derive(Clone, Eq, PartialEq)] +pub enum ConnectionState { + Disconnected, + Connected { url: String, token: String }, } -struct Dispatcher(Arc); - -#[cfg(not(target_os = "windows"))] -impl livekit::dispatcher::Dispatcher for Dispatcher { - fn dispatch(&self, runnable: livekit::dispatcher::Runnable) { - self.0.dispatch(runnable, None); - } - - fn dispatch_after( - &self, - duration: std::time::Duration, - runnable: livekit::dispatcher::Runnable, - ) { - self.0.dispatch_after(duration, runnable); - } -} - -struct HttpClientAdapter(Arc); - -fn http_2_status(status: http_client::http::StatusCode) -> http_2::StatusCode { - http_2::StatusCode::from_u16(status.as_u16()) - .expect("valid status code to status code conversion") -} - -#[cfg(not(target_os = "windows"))] -impl livekit::dispatcher::HttpClient for HttpClientAdapter { - fn get( - &self, - url: &str, - ) -> Pin> + Send>> { - let http_client = self.0.clone(); - let url = url.to_string(); - Box::pin(async move { - let response = http_client - .get(&url, http_client::AsyncBody::empty(), false) - .await - .map_err(io::Error::other)?; - Ok(livekit::dispatcher::Response { - status: http_2_status(response.status()), - body: Box::pin(response.into_body()), - }) - }) - } - - fn send_async( - &self, - request: http_2::Request>, - ) -> Pin> + Send>> { - let http_client = self.0.clone(); - let mut builder = http_client::http::Request::builder() - .method(request.method().as_str()) - .uri(request.uri().to_string()); - - for (key, value) in request.headers().iter() { - builder = builder.header(key.as_str(), value.as_bytes()); - } - - if !request.extensions().is_empty() { - debug_panic!( - "Livekit sent an HTTP request with a protocol extension that Zed doesn't support!" - ); - } - - let request = builder - .body(http_client::AsyncBody::from_bytes( - request.into_body().into(), - )) - .unwrap(); - - Box::pin(async move { - let response = http_client.send(request).await.map_err(io::Error::other)?; - Ok(livekit::dispatcher::Response { - status: http_2_status(response.status()), - body: Box::pin(response.into_body()), - }) - }) - } -} - -#[cfg(target_os = "windows")] -pub fn init( - dispatcher: Arc, - http_client: Arc, -) { -} - -#[cfg(not(target_os = "windows"))] -pub fn init( - dispatcher: Arc, - http_client: Arc, -) { - livekit::dispatcher::set_dispatcher(Dispatcher(dispatcher)); - livekit::dispatcher::set_http_client(HttpClientAdapter(http_client)); -} - -#[cfg(not(target_os = "windows"))] -pub async fn capture_local_video_track( - capture_source: &dyn ScreenCaptureSource, -) -> Result<(track::LocalVideoTrack, Box)> { - let resolution = capture_source.resolution()?; - let track_source = NativeVideoSource::new(VideoResolution { - width: resolution.width.0 as u32, - height: resolution.height.0 as u32, - }); - - let capture_stream = capture_source - .stream({ - let track_source = track_source.clone(); - Box::new(move |frame| { - if let Some(buffer) = video_frame_buffer_to_webrtc(frame) { - track_source.capture_frame(&VideoFrame { - rotation: VideoRotation::VideoRotation0, - timestamp_us: 0, - buffer, - }); - } - }) - }) - .await??; - - Ok(( - track::LocalVideoTrack::create_video_track( - "screen share", - RtcVideoSource::Native(track_source), - ), - capture_stream, - )) -} - -#[cfg(not(target_os = "windows"))] -pub fn capture_local_audio_track( - cx: &mut AppContext, -) -> Result<(track::LocalAudioTrack, AudioStream)> { - let (frame_tx, mut frame_rx) = futures::channel::mpsc::unbounded(); - - let sample_rate; - let channels; - let stream; - if cfg!(any(test, feature = "test-support")) { - sample_rate = 1; - channels = 1; - stream = None; - } else { - let device = cpal::default_host() - .default_input_device() - .ok_or_else(|| anyhow!("no audio input device available"))?; - let config = device - .default_input_config() - .context("failed to get default input config")?; - sample_rate = config.sample_rate().0; - channels = config.channels() as u32; - stream = Some( - device - .build_input_stream_raw( - &config.config(), - cpal::SampleFormat::I16, - move |data, _: &_| { - frame_tx - .unbounded_send(AudioFrame { - data: Cow::Owned(data.as_slice::().unwrap().to_vec()), - sample_rate, - num_channels: channels, - samples_per_channel: data.len() as u32 / channels, - }) - .ok(); - }, - |err| log::error!("error capturing audio track: {:?}", err), - None, - ) - .context("failed to build input stream")?, - ); - } - - let source = NativeAudioSource::new( - AudioSourceOptions { - echo_cancellation: true, - noise_suppression: true, - auto_gain_control: false, - }, - sample_rate, - channels, - // TODO livekit: Pull these out of a proto later - 100, - ); - - let stream_task = cx.foreground_executor().spawn(async move { - if let Some(stream) = &stream { - stream.play().log_err(); - } - futures::future::pending().await - }); - - let transmit_task = cx.background_executor().spawn({ - let source = source.clone(); - async move { - while let Some(frame) = frame_rx.next().await { - source.capture_frame(&frame).await.ok(); - } - Some(()) - } - }); - - let track = - track::LocalAudioTrack::create_audio_track("microphone", RtcAudioSource::Native(source)); - - Ok(( - track, - AudioStream { - _tasks: [stream_task, transmit_task], - }, - )) -} - -#[cfg(not(target_os = "windows"))] -pub fn play_remote_audio_track( - track: &track::RemoteAudioTrack, - cx: &mut AppContext, -) -> AudioStream { - let buffer = Arc::new(Mutex::new(Vec::::new())); - let (stream_config_tx, mut stream_config_rx) = futures::channel::mpsc::unbounded(); - // TODO livekit: Pull these out of a proto later - let mut stream = NativeAudioStream::new(track.rtc_track(), 48000, 1); - - let receive_task = cx.background_executor().spawn({ - let buffer = buffer.clone(); - async move { - let mut stream_config = None; - while let Some(frame) = stream.next().await { - let mut buffer = buffer.lock(); - let buffer_size = frame.samples_per_channel * frame.num_channels; - debug_assert!(frame.data.len() == buffer_size as usize); - - let frame_config = StreamConfig { - channels: frame.num_channels as u16, - sample_rate: cpal::SampleRate(frame.sample_rate), - buffer_size: cpal::BufferSize::Fixed(buffer_size), - }; - - if stream_config.as_ref().map_or(true, |c| *c != frame_config) { - buffer.resize(buffer_size as usize, 0); - stream_config = Some(frame_config.clone()); - stream_config_tx.unbounded_send(frame_config).ok(); - } - - if frame.data.len() == buffer.len() { - buffer.copy_from_slice(&frame.data); - } else { - buffer.iter_mut().for_each(|x| *x = 0); - } - } - Some(()) - } - }); - - let play_task = cx.foreground_executor().spawn( - { - let buffer = buffer.clone(); - async move { - if cfg!(any(test, feature = "test-support")) { - return Err(anyhow!("can't play audio in tests")); - } - - let device = cpal::default_host() - .default_output_device() - .ok_or_else(|| anyhow!("no audio output device available"))?; - - let mut _output_stream = None; - while let Some(config) = stream_config_rx.next().await { - _output_stream = Some(device.build_output_stream( - &config, - { - let buffer = buffer.clone(); - move |data, _info| { - let buffer = buffer.lock(); - if data.len() == buffer.len() { - data.copy_from_slice(&buffer); - } else { - data.iter_mut().for_each(|x| *x = 0); - } - } - }, - |error| log::error!("error playing audio track: {:?}", error), - None, - )?); - } - - Ok(()) - } - } - .log_err(), - ); - - AudioStream { - _tasks: [receive_task, play_task], - } -} - -#[cfg(target_os = "windows")] -pub fn play_remote_video_track( - track: &track::RemoteVideoTrack, -) -> impl Stream { - futures::stream::empty() -} - -#[cfg(not(target_os = "windows"))] -pub fn play_remote_video_track( - track: &track::RemoteVideoTrack, -) -> impl Stream { - NativeVideoStream::new(track.rtc_track()) - .filter_map(|frame| async move { video_frame_buffer_from_webrtc(frame.buffer) }) -} - -#[cfg(target_os = "macos")] -fn video_frame_buffer_from_webrtc(buffer: Box) -> Option { - use core_foundation::base::TCFType as _; - use media::core_video::CVImageBuffer; - - let buffer = buffer.as_native()?; - let pixel_buffer = buffer.get_cv_pixel_buffer(); - if pixel_buffer.is_null() { - return None; - } - - unsafe { - Some(ScreenCaptureFrame(CVImageBuffer::wrap_under_get_rule( - pixel_buffer as _, - ))) - } -} - -#[cfg(not(any(target_os = "macos", target_os = "windows")))] -fn video_frame_buffer_from_webrtc(_buffer: Box) -> Option { - None -} - -#[cfg(target_os = "macos")] -fn video_frame_buffer_to_webrtc(frame: ScreenCaptureFrame) -> Option> { - use core_foundation::base::TCFType as _; - - let pixel_buffer = frame.0.as_concrete_TypeRef(); - std::mem::forget(frame.0); - unsafe { - Some(webrtc::video_frame::native::NativeBuffer::from_cv_pixel_buffer(pixel_buffer as _)) - } -} - -#[cfg(not(any(target_os = "macos", target_os = "windows")))] -fn video_frame_buffer_to_webrtc(_frame: ScreenCaptureFrame) -> Option> { - None as Option> +#[derive(Clone)] +pub enum RoomUpdate { + ActiveSpeakersChanged { speakers: Vec }, + RemoteAudioTrackMuteChanged { track_id: Sid, muted: bool }, + SubscribedToRemoteVideoTrack(Arc), + SubscribedToRemoteAudioTrack(Arc, Arc), + UnsubscribedFromRemoteVideoTrack { publisher_id: Sid, track_id: Sid }, + UnsubscribedFromRemoteAudioTrack { publisher_id: Sid, track_id: Sid }, + LocalAudioTrackPublished { publication: LocalTrackPublication }, + LocalAudioTrackUnpublished { publication: LocalTrackPublication }, + LocalVideoTrackPublished { publication: LocalTrackPublication }, + LocalVideoTrackUnpublished { publication: LocalTrackPublication }, } diff --git a/crates/live_kit_client/src/prod.rs b/crates/live_kit_client/src/prod.rs new file mode 100644 index 0000000000..f7452da710 --- /dev/null +++ b/crates/live_kit_client/src/prod.rs @@ -0,0 +1,981 @@ +use crate::{ConnectionState, RoomUpdate, Sid}; +use anyhow::{anyhow, Context, Result}; +use core_foundation::{ + array::{CFArray, CFArrayRef}, + base::{CFRelease, CFRetain, TCFType}, + string::{CFString, CFStringRef}, +}; +use futures::{ + channel::{mpsc, oneshot}, + Future, +}; +pub use media::core_video::CVImageBuffer; +use media::core_video::CVImageBufferRef; +use parking_lot::Mutex; +use postage::watch; +use std::{ + ffi::c_void, + sync::{Arc, Weak}, +}; + +macro_rules! pointer_type { + ($pointer_name:ident) => { + #[repr(transparent)] + #[derive(Copy, Clone, Debug)] + pub struct $pointer_name(pub *const std::ffi::c_void); + unsafe impl Send for $pointer_name {} + }; +} + +mod swift { + pointer_type!(Room); + pointer_type!(LocalAudioTrack); + pointer_type!(RemoteAudioTrack); + pointer_type!(LocalVideoTrack); + pointer_type!(RemoteVideoTrack); + pointer_type!(LocalTrackPublication); + pointer_type!(RemoteTrackPublication); + pointer_type!(MacOSDisplay); + pointer_type!(RoomDelegate); +} + +extern "C" { + fn LKRoomDelegateCreate( + callback_data: *mut c_void, + on_did_disconnect: extern "C" fn(callback_data: *mut c_void), + on_did_subscribe_to_remote_audio_track: extern "C" fn( + callback_data: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + remote_track: swift::RemoteAudioTrack, + remote_publication: swift::RemoteTrackPublication, + ), + on_did_unsubscribe_from_remote_audio_track: extern "C" fn( + callback_data: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + ), + on_mute_changed_from_remote_audio_track: extern "C" fn( + callback_data: *mut c_void, + track_id: CFStringRef, + muted: bool, + ), + on_active_speakers_changed: extern "C" fn( + callback_data: *mut c_void, + participants: CFArrayRef, + ), + on_did_subscribe_to_remote_video_track: extern "C" fn( + callback_data: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + remote_track: swift::RemoteVideoTrack, + ), + on_did_unsubscribe_from_remote_video_track: extern "C" fn( + callback_data: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + ), + on_did_publish_or_unpublish_local_audio_track: extern "C" fn( + callback_data: *mut c_void, + publication: swift::LocalTrackPublication, + is_published: bool, + ), + on_did_publish_or_unpublish_local_video_track: extern "C" fn( + callback_data: *mut c_void, + publication: swift::LocalTrackPublication, + is_published: bool, + ), + ) -> swift::RoomDelegate; + + fn LKRoomCreate(delegate: swift::RoomDelegate) -> swift::Room; + fn LKRoomConnect( + room: swift::Room, + url: CFStringRef, + token: CFStringRef, + callback: extern "C" fn(*mut c_void, CFStringRef), + callback_data: *mut c_void, + ); + fn LKRoomDisconnect(room: swift::Room); + fn LKRoomPublishVideoTrack( + room: swift::Room, + track: swift::LocalVideoTrack, + callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef), + callback_data: *mut c_void, + ); + fn LKRoomPublishAudioTrack( + room: swift::Room, + track: swift::LocalAudioTrack, + callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef), + callback_data: *mut c_void, + ); + fn LKRoomUnpublishTrack(room: swift::Room, publication: swift::LocalTrackPublication); + + fn LKRoomAudioTracksForRemoteParticipant( + room: swift::Room, + participant_id: CFStringRef, + ) -> CFArrayRef; + + fn LKRoomAudioTrackPublicationsForRemoteParticipant( + room: swift::Room, + participant_id: CFStringRef, + ) -> CFArrayRef; + + fn LKRoomVideoTracksForRemoteParticipant( + room: swift::Room, + participant_id: CFStringRef, + ) -> CFArrayRef; + + fn LKVideoRendererCreate( + callback_data: *mut c_void, + on_frame: extern "C" fn(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool, + on_drop: extern "C" fn(callback_data: *mut c_void), + ) -> *const c_void; + + fn LKRemoteAudioTrackGetSid(track: swift::RemoteAudioTrack) -> CFStringRef; + fn LKRemoteVideoTrackGetSid(track: swift::RemoteVideoTrack) -> CFStringRef; + fn LKRemoteAudioTrackStart(track: swift::RemoteAudioTrack); + fn LKRemoteAudioTrackStop(track: swift::RemoteAudioTrack); + fn LKVideoTrackAddRenderer(track: swift::RemoteVideoTrack, renderer: *const c_void); + + fn LKDisplaySources( + callback_data: *mut c_void, + callback: extern "C" fn( + callback_data: *mut c_void, + sources: CFArrayRef, + error: CFStringRef, + ), + ); + fn LKCreateScreenShareTrackForDisplay(display: swift::MacOSDisplay) -> swift::LocalVideoTrack; + fn LKLocalAudioTrackCreateTrack() -> swift::LocalAudioTrack; + + fn LKLocalTrackPublicationSetMute( + publication: swift::LocalTrackPublication, + muted: bool, + on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef), + callback_data: *mut c_void, + ); + + fn LKRemoteTrackPublicationSetEnabled( + publication: swift::RemoteTrackPublication, + enabled: bool, + on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef), + callback_data: *mut c_void, + ); + + fn LKLocalTrackPublicationIsMuted(publication: swift::LocalTrackPublication) -> bool; + fn LKRemoteTrackPublicationIsMuted(publication: swift::RemoteTrackPublication) -> bool; + fn LKLocalTrackPublicationGetSid(publication: swift::LocalTrackPublication) -> CFStringRef; + fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef; +} + +pub struct Room { + native_room: swift::Room, + connection: Mutex<( + watch::Sender, + watch::Receiver, + )>, + update_subscribers: Mutex>>, + _delegate: RoomDelegate, +} + +impl Room { + pub fn new() -> Arc { + Arc::new_cyclic(|weak_room| { + let delegate = RoomDelegate::new(weak_room.clone()); + Self { + native_room: unsafe { LKRoomCreate(delegate.native_delegate) }, + connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)), + update_subscribers: Default::default(), + _delegate: delegate, + } + }) + } + + pub fn status(&self) -> watch::Receiver { + self.connection.lock().1.clone() + } + + pub fn connect(self: &Arc, url: &str, token: &str) -> impl Future> { + let url = CFString::new(url); + let token = CFString::new(token); + let (did_connect, tx, rx) = Self::build_done_callback(); + unsafe { + LKRoomConnect( + self.native_room, + url.as_concrete_TypeRef(), + token.as_concrete_TypeRef(), + did_connect, + tx, + ) + } + + let this = self.clone(); + let url = url.to_string(); + let token = token.to_string(); + async move { + rx.await.unwrap().context("error connecting to room")?; + *this.connection.lock().0.borrow_mut() = ConnectionState::Connected { url, token }; + Ok(()) + } + } + + fn did_disconnect(&self) { + *self.connection.lock().0.borrow_mut() = ConnectionState::Disconnected; + } + + pub fn display_sources(self: &Arc) -> impl Future>> { + extern "C" fn callback(tx: *mut c_void, sources: CFArrayRef, error: CFStringRef) { + unsafe { + let tx = Box::from_raw(tx as *mut oneshot::Sender>>); + + if sources.is_null() { + let _ = tx.send(Err(anyhow!("{}", CFString::wrap_under_get_rule(error)))); + } else { + let sources = CFArray::wrap_under_get_rule(sources) + .into_iter() + .map(|source| MacOSDisplay::new(swift::MacOSDisplay(*source))) + .collect(); + + let _ = tx.send(Ok(sources)); + } + } + } + + let (tx, rx) = oneshot::channel(); + + unsafe { + LKDisplaySources(Box::into_raw(Box::new(tx)) as *mut _, callback); + } + + async move { rx.await.unwrap() } + } + + pub fn publish_video_track( + self: &Arc, + track: LocalVideoTrack, + ) -> impl Future> { + let (tx, rx) = oneshot::channel::>(); + extern "C" fn callback( + tx: *mut c_void, + publication: swift::LocalTrackPublication, + error: CFStringRef, + ) { + let tx = + unsafe { Box::from_raw(tx as *mut oneshot::Sender>) }; + if error.is_null() { + let _ = tx.send(Ok(LocalTrackPublication::new(publication))); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + let _ = tx.send(Err(anyhow!(error))); + } + } + unsafe { + LKRoomPublishVideoTrack( + self.native_room, + track.0, + callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + ); + } + async { rx.await.unwrap().context("error publishing video track") } + } + + pub fn publish_audio_track( + self: &Arc, + track: LocalAudioTrack, + ) -> impl Future> { + let (tx, rx) = oneshot::channel::>(); + extern "C" fn callback( + tx: *mut c_void, + publication: swift::LocalTrackPublication, + error: CFStringRef, + ) { + let tx = + unsafe { Box::from_raw(tx as *mut oneshot::Sender>) }; + if error.is_null() { + let _ = tx.send(Ok(LocalTrackPublication::new(publication))); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + let _ = tx.send(Err(anyhow!(error))); + } + } + unsafe { + LKRoomPublishAudioTrack( + self.native_room, + track.0, + callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + ); + } + async { rx.await.unwrap().context("error publishing audio track") } + } + + pub fn unpublish_track(&self, publication: LocalTrackPublication) { + unsafe { + LKRoomUnpublishTrack(self.native_room, publication.0); + } + } + + pub fn remote_video_tracks(&self, participant_id: &str) -> Vec> { + unsafe { + let tracks = LKRoomVideoTracksForRemoteParticipant( + self.native_room, + CFString::new(participant_id).as_concrete_TypeRef(), + ); + + if tracks.is_null() { + Vec::new() + } else { + let tracks = CFArray::wrap_under_get_rule(tracks); + tracks + .into_iter() + .map(|native_track| { + let native_track = swift::RemoteVideoTrack(*native_track); + let id = + CFString::wrap_under_get_rule(LKRemoteVideoTrackGetSid(native_track)) + .to_string(); + Arc::new(RemoteVideoTrack::new( + native_track, + id, + participant_id.into(), + )) + }) + .collect() + } + } + } + + pub fn remote_audio_tracks(&self, participant_id: &str) -> Vec> { + unsafe { + let tracks = LKRoomAudioTracksForRemoteParticipant( + self.native_room, + CFString::new(participant_id).as_concrete_TypeRef(), + ); + + if tracks.is_null() { + Vec::new() + } else { + let tracks = CFArray::wrap_under_get_rule(tracks); + tracks + .into_iter() + .map(|native_track| { + let native_track = swift::RemoteAudioTrack(*native_track); + let id = + CFString::wrap_under_get_rule(LKRemoteAudioTrackGetSid(native_track)) + .to_string(); + Arc::new(RemoteAudioTrack::new( + native_track, + id, + participant_id.into(), + )) + }) + .collect() + } + } + } + + pub fn remote_audio_track_publications( + &self, + participant_id: &str, + ) -> Vec> { + unsafe { + let tracks = LKRoomAudioTrackPublicationsForRemoteParticipant( + self.native_room, + CFString::new(participant_id).as_concrete_TypeRef(), + ); + + if tracks.is_null() { + Vec::new() + } else { + let tracks = CFArray::wrap_under_get_rule(tracks); + tracks + .into_iter() + .map(|native_track_publication| { + let native_track_publication = + swift::RemoteTrackPublication(*native_track_publication); + Arc::new(RemoteTrackPublication::new(native_track_publication)) + }) + .collect() + } + } + } + + pub fn updates(&self) -> mpsc::UnboundedReceiver { + let (tx, rx) = mpsc::unbounded(); + self.update_subscribers.lock().push(tx); + rx + } + + fn did_subscribe_to_remote_audio_track( + &self, + track: RemoteAudioTrack, + publication: RemoteTrackPublication, + ) { + let track = Arc::new(track); + let publication = Arc::new(publication); + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::SubscribedToRemoteAudioTrack( + track.clone(), + publication.clone(), + )) + .is_ok() + }); + } + + fn did_unsubscribe_from_remote_audio_track(&self, publisher_id: String, track_id: String) { + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::UnsubscribedFromRemoteAudioTrack { + publisher_id: publisher_id.clone(), + track_id: track_id.clone(), + }) + .is_ok() + }); + } + + fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) { + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::RemoteAudioTrackMuteChanged { + track_id: track_id.clone(), + muted, + }) + .is_ok() + }); + } + + fn active_speakers_changed(&self, speakers: Vec) { + self.update_subscribers.lock().retain(move |tx| { + tx.unbounded_send(RoomUpdate::ActiveSpeakersChanged { + speakers: speakers.clone(), + }) + .is_ok() + }); + } + + fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) { + let track = Arc::new(track); + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) + .is_ok() + }); + } + + fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) { + self.update_subscribers.lock().retain(|tx| { + tx.unbounded_send(RoomUpdate::UnsubscribedFromRemoteVideoTrack { + publisher_id: publisher_id.clone(), + track_id: track_id.clone(), + }) + .is_ok() + }); + } + + fn build_done_callback() -> ( + extern "C" fn(*mut c_void, CFStringRef), + *mut c_void, + oneshot::Receiver>, + ) { + let (tx, rx) = oneshot::channel(); + extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) { + let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender>) }; + if error.is_null() { + let _ = tx.send(Ok(())); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + let _ = tx.send(Err(anyhow!(error))); + } + } + ( + done_callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + rx, + ) + } + + pub fn set_display_sources(&self, _: Vec) { + unreachable!("This is a test-only function") + } +} + +impl Drop for Room { + fn drop(&mut self) { + unsafe { + LKRoomDisconnect(self.native_room); + CFRelease(self.native_room.0); + } + } +} + +struct RoomDelegate { + native_delegate: swift::RoomDelegate, + weak_room: *mut c_void, +} + +impl RoomDelegate { + fn new(weak_room: Weak) -> Self { + let weak_room = weak_room.into_raw() as *mut c_void; + let native_delegate = unsafe { + LKRoomDelegateCreate( + weak_room, + Self::on_did_disconnect, + Self::on_did_subscribe_to_remote_audio_track, + Self::on_did_unsubscribe_from_remote_audio_track, + Self::on_mute_change_from_remote_audio_track, + Self::on_active_speakers_changed, + Self::on_did_subscribe_to_remote_video_track, + Self::on_did_unsubscribe_from_remote_video_track, + Self::on_did_publish_or_unpublish_local_audio_track, + Self::on_did_publish_or_unpublish_local_video_track, + ) + }; + Self { + native_delegate, + weak_room, + } + } + + extern "C" fn on_did_disconnect(room: *mut c_void) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + if let Some(room) = room.upgrade() { + room.did_disconnect(); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_subscribe_to_remote_audio_track( + room: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + track: swift::RemoteAudioTrack, + publication: swift::RemoteTrackPublication, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + let track = RemoteAudioTrack::new(track, track_id, publisher_id); + let publication = RemoteTrackPublication::new(publication); + if let Some(room) = room.upgrade() { + room.did_subscribe_to_remote_audio_track(track, publication); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_unsubscribe_from_remote_audio_track( + room: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + if let Some(room) = room.upgrade() { + room.did_unsubscribe_from_remote_audio_track(publisher_id, track_id); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_mute_change_from_remote_audio_track( + room: *mut c_void, + track_id: CFStringRef, + muted: bool, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + if let Some(room) = room.upgrade() { + room.mute_changed_from_remote_audio_track(track_id, muted); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_active_speakers_changed(room: *mut c_void, participants: CFArrayRef) { + if participants.is_null() { + return; + } + + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let speakers = unsafe { + CFArray::wrap_under_get_rule(participants) + .into_iter() + .map( + |speaker: core_foundation::base::ItemRef<'_, *const c_void>| { + CFString::wrap_under_get_rule(*speaker as CFStringRef).to_string() + }, + ) + .collect() + }; + + if let Some(room) = room.upgrade() { + room.active_speakers_changed(speakers); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_subscribe_to_remote_video_track( + room: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + track: swift::RemoteVideoTrack, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + let track = RemoteVideoTrack::new(track, track_id, publisher_id); + if let Some(room) = room.upgrade() { + room.did_subscribe_to_remote_video_track(track); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_unsubscribe_from_remote_video_track( + room: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + if let Some(room) = room.upgrade() { + room.did_unsubscribe_from_remote_video_track(publisher_id, track_id); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_publish_or_unpublish_local_audio_track( + room: *mut c_void, + publication: swift::LocalTrackPublication, + is_published: bool, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + if let Some(room) = room.upgrade() { + let publication = LocalTrackPublication::new(publication); + let update = if is_published { + RoomUpdate::LocalAudioTrackPublished { publication } + } else { + RoomUpdate::LocalAudioTrackUnpublished { publication } + }; + room.update_subscribers + .lock() + .retain(|tx| tx.unbounded_send(update.clone()).is_ok()); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_publish_or_unpublish_local_video_track( + room: *mut c_void, + publication: swift::LocalTrackPublication, + is_published: bool, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + if let Some(room) = room.upgrade() { + let publication = LocalTrackPublication::new(publication); + let update = if is_published { + RoomUpdate::LocalVideoTrackPublished { publication } + } else { + RoomUpdate::LocalVideoTrackUnpublished { publication } + }; + room.update_subscribers + .lock() + .retain(|tx| tx.unbounded_send(update.clone()).is_ok()); + } + let _ = Weak::into_raw(room); + } +} + +impl Drop for RoomDelegate { + fn drop(&mut self) { + unsafe { + CFRelease(self.native_delegate.0); + let _ = Weak::from_raw(self.weak_room as *mut Room); + } + } +} + +pub struct LocalAudioTrack(swift::LocalAudioTrack); + +impl LocalAudioTrack { + pub fn create() -> Self { + Self(unsafe { LKLocalAudioTrackCreateTrack() }) + } +} + +impl Drop for LocalAudioTrack { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +pub struct LocalVideoTrack(swift::LocalVideoTrack); + +impl LocalVideoTrack { + pub fn screen_share_for_display(display: &MacOSDisplay) -> Self { + Self(unsafe { LKCreateScreenShareTrackForDisplay(display.0) }) + } +} + +impl Drop for LocalVideoTrack { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +pub struct LocalTrackPublication(swift::LocalTrackPublication); + +impl LocalTrackPublication { + pub fn new(native_track_publication: swift::LocalTrackPublication) -> Self { + unsafe { + CFRetain(native_track_publication.0); + } + Self(native_track_publication) + } + + pub fn sid(&self) -> String { + unsafe { CFString::wrap_under_get_rule(LKLocalTrackPublicationGetSid(self.0)).to_string() } + } + + pub fn set_mute(&self, muted: bool) -> impl Future> { + let (tx, rx) = futures::channel::oneshot::channel(); + + extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) { + let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender>) }; + if error.is_null() { + tx.send(Ok(())).ok(); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + tx.send(Err(anyhow!(error))).ok(); + } + } + + unsafe { + LKLocalTrackPublicationSetMute( + self.0, + muted, + complete_callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + ) + } + + async move { rx.await.unwrap() } + } + + pub fn is_muted(&self) -> bool { + unsafe { LKLocalTrackPublicationIsMuted(self.0) } + } +} + +impl Clone for LocalTrackPublication { + fn clone(&self) -> Self { + unsafe { + CFRetain(self.0 .0); + } + Self(self.0) + } +} + +impl Drop for LocalTrackPublication { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +pub struct RemoteTrackPublication(swift::RemoteTrackPublication); + +impl RemoteTrackPublication { + pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self { + unsafe { + CFRetain(native_track_publication.0); + } + Self(native_track_publication) + } + + pub fn sid(&self) -> String { + unsafe { CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid(self.0)).to_string() } + } + + pub fn is_muted(&self) -> bool { + unsafe { LKRemoteTrackPublicationIsMuted(self.0) } + } + + pub fn set_enabled(&self, enabled: bool) -> impl Future> { + let (tx, rx) = futures::channel::oneshot::channel(); + + extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) { + let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender>) }; + if error.is_null() { + tx.send(Ok(())).ok(); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + tx.send(Err(anyhow!(error))).ok(); + } + } + + unsafe { + LKRemoteTrackPublicationSetEnabled( + self.0, + enabled, + complete_callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + ) + } + + async move { rx.await.unwrap() } + } +} + +impl Drop for RemoteTrackPublication { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +#[derive(Debug)] +pub struct RemoteAudioTrack { + native_track: swift::RemoteAudioTrack, + sid: Sid, + publisher_id: String, +} + +impl RemoteAudioTrack { + fn new(native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String) -> Self { + unsafe { + CFRetain(native_track.0); + } + Self { + native_track, + sid, + publisher_id, + } + } + + pub fn sid(&self) -> &str { + &self.sid + } + + pub fn publisher_id(&self) -> &str { + &self.publisher_id + } + + pub fn start(&self) { + unsafe { LKRemoteAudioTrackStart(self.native_track) } + } + + pub fn stop(&self) { + unsafe { LKRemoteAudioTrackStop(self.native_track) } + } +} + +impl Drop for RemoteAudioTrack { + fn drop(&mut self) { + // todo: uncomment this `CFRelease`, unless we find that it was causing + // the crash in the `livekit.multicast` thread. + // + // unsafe { CFRelease(self.native_track.0) } + let _ = self.native_track; + } +} + +#[derive(Debug)] +pub struct RemoteVideoTrack { + native_track: swift::RemoteVideoTrack, + sid: Sid, + publisher_id: String, +} + +impl RemoteVideoTrack { + fn new(native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String) -> Self { + unsafe { + CFRetain(native_track.0); + } + Self { + native_track, + sid, + publisher_id, + } + } + + pub fn sid(&self) -> &str { + &self.sid + } + + pub fn publisher_id(&self) -> &str { + &self.publisher_id + } + + pub fn frames(&self) -> async_broadcast::Receiver { + extern "C" fn on_frame(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool { + unsafe { + let tx = Box::from_raw(callback_data as *mut async_broadcast::Sender); + let buffer = CVImageBuffer::wrap_under_get_rule(frame); + let result = tx.try_broadcast(Frame(buffer)); + let _ = Box::into_raw(tx); + match result { + Ok(_) => true, + Err(async_broadcast::TrySendError::Closed(_)) + | Err(async_broadcast::TrySendError::Inactive(_)) => { + log::warn!("no active receiver for frame"); + false + } + Err(async_broadcast::TrySendError::Full(_)) => { + log::warn!("skipping frame as receiver is not keeping up"); + true + } + } + } + } + + extern "C" fn on_drop(callback_data: *mut c_void) { + unsafe { + let _ = Box::from_raw(callback_data as *mut async_broadcast::Sender); + } + } + + let (tx, rx) = async_broadcast::broadcast(64); + unsafe { + let renderer = LKVideoRendererCreate( + Box::into_raw(Box::new(tx)) as *mut c_void, + on_frame, + on_drop, + ); + LKVideoTrackAddRenderer(self.native_track, renderer); + rx + } + } +} + +impl Drop for RemoteVideoTrack { + fn drop(&mut self) { + unsafe { CFRelease(self.native_track.0) } + } +} + +pub struct MacOSDisplay(swift::MacOSDisplay); + +impl MacOSDisplay { + fn new(ptr: swift::MacOSDisplay) -> Self { + unsafe { + CFRetain(ptr.0); + } + Self(ptr) + } +} + +impl Drop for MacOSDisplay { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +#[derive(Clone)] +pub struct Frame(CVImageBuffer); + +impl Frame { + pub fn width(&self) -> usize { + self.0.width() + } + + pub fn height(&self) -> usize { + self.0.height() + } + + pub fn image(&self) -> CVImageBuffer { + self.0.clone() + } +} diff --git a/crates/live_kit_client/src/remote_video_track_view.rs b/crates/live_kit_client/src/remote_video_track_view.rs deleted file mode 100644 index bbfaea1875..0000000000 --- a/crates/live_kit_client/src/remote_video_track_view.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::track::RemoteVideoTrack; -use anyhow::Result; -use futures::StreamExt as _; -use gpui::{ - Empty, EventEmitter, IntoElement, Render, ScreenCaptureFrame, Task, View, ViewContext, - VisualContext as _, -}; - -pub struct RemoteVideoTrackView { - track: RemoteVideoTrack, - frame: Option, - _maintain_frame: Task>, -} - -#[derive(Debug)] -pub enum RemoteVideoTrackViewEvent { - Close, -} - -impl RemoteVideoTrackView { - pub fn new(track: RemoteVideoTrack, cx: &mut ViewContext) -> Self { - cx.focus_handle(); - let frames = super::play_remote_video_track(&track); - - Self { - track, - frame: None, - _maintain_frame: cx.spawn(|this, mut cx| async move { - futures::pin_mut!(frames); - while let Some(frame) = frames.next().await { - this.update(&mut cx, |this, cx| { - this.frame = Some(frame); - cx.notify(); - })?; - } - this.update(&mut cx, |_, cx| cx.emit(RemoteVideoTrackViewEvent::Close))?; - Ok(()) - }), - } - } - - pub fn clone(&self, cx: &mut ViewContext) -> View { - cx.new_view(|cx| Self::new(self.track.clone(), cx)) - } -} - -impl EventEmitter for RemoteVideoTrackView {} - -impl Render for RemoteVideoTrackView { - fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - #[cfg(target_os = "macos")] - if let Some(frame) = &self.frame { - use gpui::Styled as _; - return gpui::surface(frame.0.clone()) - .size_full() - .into_any_element(); - } - - Empty.into_any_element() - } -} diff --git a/crates/live_kit_client/src/test.rs b/crates/live_kit_client/src/test.rs index 1bd2e60d17..2c26c88f72 100644 --- a/crates/live_kit_client/src/test.rs +++ b/crates/live_kit_client/src/test.rs @@ -1,29 +1,21 @@ -pub mod participant; -pub mod publication; -pub mod track; - -#[cfg(not(windows))] -pub mod webrtc; - -#[cfg(not(windows))] -use self::id::*; -use self::{participant::*, publication::*, track::*}; +use crate::{ConnectionState, RoomUpdate, Sid}; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use collections::{btree_map::Entry as BTreeEntry, hash_map::Entry, BTreeMap, HashMap, HashSet}; -use gpui::BackgroundExecutor; +use futures::Stream; +use gpui::{BackgroundExecutor, SurfaceSource}; use live_kit_server::{proto, token}; -#[cfg(not(windows))] -use livekit::options::TrackPublishOptions; -use parking_lot::Mutex; -use postage::{mpsc, sink::Sink}; -use std::sync::{ - atomic::{AtomicBool, Ordering::SeqCst}, - Arc, Weak, -}; -#[cfg(not(windows))] -pub use livekit::{id, options, ConnectionState, DisconnectReason, RoomOptions}; +use parking_lot::Mutex; +use postage::watch; +use std::{ + future::Future, + mem, + sync::{ + atomic::{AtomicBool, Ordering::SeqCst}, + Arc, Weak, + }, +}; static SERVERS: Mutex>> = Mutex::new(BTreeMap::new()); @@ -31,12 +23,10 @@ pub struct TestServer { pub url: String, pub api_key: String, pub secret_key: String, - #[cfg(not(target_os = "windows"))] rooms: Mutex>, executor: BackgroundExecutor, } -#[cfg(not(target_os = "windows"))] impl TestServer { pub fn create( url: String, @@ -83,8 +73,9 @@ impl TestServer { } pub async fn create_room(&self, room: String) -> Result<()> { + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged + #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; - let mut server_rooms = self.rooms.lock(); if let Entry::Vacant(e) = server_rooms.entry(room.clone()) { e.insert(Default::default()); @@ -95,8 +86,10 @@ impl TestServer { } async fn delete_room(&self, room: String) -> Result<()> { + // TODO: clear state associated with all `Room`s. + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged + #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; - let mut server_rooms = self.rooms.lock(); server_rooms .remove(&room) @@ -104,64 +97,46 @@ impl TestServer { Ok(()) } - async fn join_room(&self, token: String, client_room: Room) -> Result { + async fn join_room(&self, token: String, client_room: Arc) -> Result<()> { + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged + #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; let claims = live_kit_server::token::validate(&token, &self.secret_key)?; - let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); + let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); let room = (*server_rooms).entry(room_name.to_string()).or_default(); if let Entry::Vacant(e) = room.client_rooms.entry(identity.clone()) { - for server_track in &room.video_tracks { - let track = RemoteTrack::Video(RemoteVideoTrack { - server_track: server_track.clone(), - _room: client_room.downgrade(), - }); + for track in &room.video_tracks { client_room .0 .lock() .updates_tx - .blocking_send(RoomEvent::TrackSubscribed { - track: track.clone(), - publication: RemoteTrackPublication { - sid: server_track.sid.clone(), - room: client_room.downgrade(), - track, + .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(Arc::new( + RemoteVideoTrack { + server_track: track.clone(), }, - participant: RemoteParticipant { - room: client_room.downgrade(), - identity: server_track.publisher_id.clone(), - }, - }) + ))) .unwrap(); } - for server_track in &room.audio_tracks { - let track = RemoteTrack::Audio(RemoteAudioTrack { - server_track: server_track.clone(), - room: client_room.downgrade(), - }); + for track in &room.audio_tracks { client_room .0 .lock() .updates_tx - .blocking_send(RoomEvent::TrackSubscribed { - track: track.clone(), - publication: RemoteTrackPublication { - sid: server_track.sid.clone(), - room: client_room.downgrade(), - track, - }, - participant: RemoteParticipant { - room: client_room.downgrade(), - identity: server_track.publisher_id.clone(), - }, - }) + .try_broadcast(RoomUpdate::SubscribedToRemoteAudioTrack( + Arc::new(RemoteAudioTrack { + server_track: track.clone(), + room: Arc::downgrade(&client_room), + }), + Arc::new(RemoteTrackPublication), + )) .unwrap(); } e.insert(client_room); - Ok(identity) + Ok(()) } else { Err(anyhow!( "{:?} attempted to join room {:?} twice", @@ -172,10 +147,11 @@ impl TestServer { } async fn leave_room(&self, token: String) -> Result<()> { + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged + #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; - let claims = live_kit_server::token::validate(&token, &self.secret_key)?; - let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); + let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); let room = server_rooms @@ -191,44 +167,10 @@ impl TestServer { Ok(()) } - fn remote_participants( - &self, - token: String, - ) -> Result> { - let claims = live_kit_server::token::validate(&token, &self.secret_key)?; - let local_identity = ParticipantIdentity(claims.sub.unwrap().to_string()); - let room_name = claims.video.room.unwrap().to_string(); - - if let Some(server_room) = self.rooms.lock().get(&room_name) { - let room = server_room - .client_rooms - .get(&local_identity) - .unwrap() - .downgrade(); - Ok(server_room - .client_rooms - .iter() - .filter(|(identity, _)| *identity != &local_identity) - .map(|(identity, _)| { - ( - identity.clone(), - RemoteParticipant { - room: room.clone(), - identity: identity.clone(), - }, - ) - }) - .collect()) - } else { - Ok(Default::default()) - } - } - - async fn remove_participant( - &self, - room_name: String, - identity: ParticipantIdentity, - ) -> Result<()> { + async fn remove_participant(&self, room_name: String, identity: String) -> Result<()> { + // TODO: clear state associated with the `Room`. + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged + #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; let mut server_rooms = self.rooms.lock(); @@ -251,32 +193,25 @@ impl TestServer { identity: String, permission: proto::ParticipantPermission, ) -> Result<()> { + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged + #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; - let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; - room.participant_permissions - .insert(ParticipantIdentity(identity), permission); + room.participant_permissions.insert(identity, permission); Ok(()) } pub async fn disconnect_client(&self, client_identity: String) { - let client_identity = ParticipantIdentity(client_identity); - + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged + #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; - let mut server_rooms = self.rooms.lock(); for room in server_rooms.values_mut() { if let Some(room) = room.client_rooms.remove(&client_identity) { - let mut room = room.0.lock(); - room.connection_state = ConnectionState::Disconnected; - room.updates_tx - .blocking_send(RoomEvent::Disconnected { - reason: DisconnectReason::SignalClose, - }) - .ok(); + *room.0.lock().connection.0.borrow_mut() = ConnectionState::Disconnected; } } } @@ -284,12 +219,13 @@ impl TestServer { async fn publish_video_track( &self, token: String, - _local_track: LocalVideoTrack, - ) -> Result { + local_track: LocalVideoTrack, + ) -> Result { + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged + #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; - let claims = live_kit_server::token::validate(&token, &self.secret_key)?; - let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); + let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); @@ -308,38 +244,26 @@ impl TestServer { return Err(anyhow!("user is not allowed to publish")); } - let sid: TrackSid = format!("TR_{}", nanoid::nanoid!(17)).try_into().unwrap(); - let server_track = Arc::new(TestServerVideoTrack { + let sid = nanoid::nanoid!(17); + let track = Arc::new(TestServerVideoTrack { sid: sid.clone(), publisher_id: identity.clone(), + frames_rx: local_track.frames_rx.clone(), }); - room.video_tracks.push(server_track.clone()); + room.video_tracks.push(track.clone()); - for (room_identity, client_room) in &room.client_rooms { - if *room_identity != identity { - let track = RemoteTrack::Video(RemoteVideoTrack { - server_track: server_track.clone(), - _room: client_room.downgrade(), - }); - let publication = RemoteTrackPublication { - sid: sid.clone(), - room: client_room.downgrade(), - track: track.clone(), - }; - let participant = RemoteParticipant { - identity: identity.clone(), - room: client_room.downgrade(), - }; - client_room + for (id, client_room) in &room.client_rooms { + if *id != identity { + let _ = client_room .0 .lock() .updates_tx - .blocking_send(RoomEvent::TrackSubscribed { - track, - publication, - participant, - }) + .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(Arc::new( + RemoteVideoTrack { + server_track: track.clone(), + }, + ))) .unwrap(); } } @@ -351,11 +275,13 @@ impl TestServer { &self, token: String, _local_track: &LocalAudioTrack, - ) -> Result { + ) -> Result { + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged + #[cfg(any(test, feature = "test-support"))] self.executor.simulate_random_delay().await; let claims = live_kit_server::token::validate(&token, &self.secret_key)?; - let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); + let identity = claims.sub.unwrap().to_string(); let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); @@ -374,54 +300,41 @@ impl TestServer { return Err(anyhow!("user is not allowed to publish")); } - let sid: TrackSid = format!("TR_{}", nanoid::nanoid!(17)).try_into().unwrap(); - let server_track = Arc::new(TestServerAudioTrack { + let sid = nanoid::nanoid!(17); + let track = Arc::new(TestServerAudioTrack { sid: sid.clone(), publisher_id: identity.clone(), muted: AtomicBool::new(false), }); - room.audio_tracks.push(server_track.clone()); + let publication = Arc::new(RemoteTrackPublication); - for (room_identity, client_room) in &room.client_rooms { - if *room_identity != identity { - let track = RemoteTrack::Audio(RemoteAudioTrack { - server_track: server_track.clone(), - room: client_room.downgrade(), - }); - let publication = RemoteTrackPublication { - sid: sid.clone(), - room: client_room.downgrade(), - track: track.clone(), - }; - let participant = RemoteParticipant { - identity: identity.clone(), - room: client_room.downgrade(), - }; - client_room + room.audio_tracks.push(track.clone()); + + for (id, client_room) in &room.client_rooms { + if *id != identity { + let _ = client_room .0 .lock() .updates_tx - .blocking_send(RoomEvent::TrackSubscribed { - track, - publication, - participant, - }) - .ok(); + .try_broadcast(RoomUpdate::SubscribedToRemoteAudioTrack( + Arc::new(RemoteAudioTrack { + server_track: track.clone(), + room: Arc::downgrade(client_room), + }), + publication.clone(), + )) + .unwrap(); } } Ok(sid) } - async fn unpublish_track(&self, _token: String, _track: &TrackSid) -> Result<()> { - Ok(()) - } - - fn set_track_muted(&self, token: &str, track_sid: &TrackSid, muted: bool) -> Result<()> { - let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + fn set_track_muted(&self, token: &str, track_sid: &str, muted: bool) -> Result<()> { + let claims = live_kit_server::token::validate(token, &self.secret_key)?; let room_name = claims.video.room.unwrap(); - let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); + let identity = claims.sub.unwrap(); let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) @@ -429,42 +342,19 @@ impl TestServer { if let Some(track) = room .audio_tracks .iter_mut() - .find(|track| track.sid == *track_sid) + .find(|track| track.sid == track_sid) { track.muted.store(muted, SeqCst); for (id, client_room) in room.client_rooms.iter() { if *id != identity { - let participant = Participant::Remote(RemoteParticipant { - identity: identity.clone(), - room: client_room.downgrade(), - }); - let track = RemoteTrack::Audio(RemoteAudioTrack { - server_track: track.clone(), - room: client_room.downgrade(), - }); - let publication = TrackPublication::Remote(RemoteTrackPublication { - sid: track_sid.clone(), - room: client_room.downgrade(), - track, - }); - - let event = if muted { - RoomEvent::TrackMuted { - participant, - publication, - } - } else { - RoomEvent::TrackUnmuted { - participant, - publication, - } - }; - client_room .0 .lock() .updates_tx - .blocking_send(event) + .try_broadcast(RoomUpdate::RemoteAudioTrackMuteChanged { + track_id: track_sid.to_string(), + muted, + }) .unwrap(); } } @@ -472,14 +362,14 @@ impl TestServer { Ok(()) } - fn is_track_muted(&self, token: &str, track_sid: &TrackSid) -> Option { - let claims = live_kit_server::token::validate(&token, &self.secret_key).ok()?; + fn is_track_muted(&self, token: &str, track_sid: &str) -> Option { + let claims = live_kit_server::token::validate(token, &self.secret_key).ok()?; let room_name = claims.video.room.unwrap(); let mut server_rooms = self.rooms.lock(); let room = server_rooms.get_mut(&*room_name)?; room.audio_tracks.iter().find_map(|track| { - if track.sid == *track_sid { + if track.sid == track_sid { Some(track.muted.load(SeqCst)) } else { None @@ -487,33 +377,33 @@ impl TestServer { }) } - fn video_tracks(&self, token: String) -> Result> { + fn video_tracks(&self, token: String) -> Result>> { let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let room_name = claims.video.room.unwrap(); - let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); + let identity = claims.sub.unwrap(); let mut server_rooms = self.rooms.lock(); let room = server_rooms .get_mut(&*room_name) .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; - let client_room = room - .client_rooms - .get(&identity) + room.client_rooms + .get(identity.as_ref()) .ok_or_else(|| anyhow!("not a participant in room"))?; Ok(room .video_tracks .iter() - .map(|track| RemoteVideoTrack { - server_track: track.clone(), - _room: client_room.downgrade(), + .map(|track| { + Arc::new(RemoteVideoTrack { + server_track: track.clone(), + }) }) .collect()) } - fn audio_tracks(&self, token: String) -> Result> { + fn audio_tracks(&self, token: String) -> Result>> { let claims = live_kit_server::token::validate(&token, &self.secret_key)?; let room_name = claims.video.room.unwrap(); - let identity = ParticipantIdentity(claims.sub.unwrap().to_string()); + let identity = claims.sub.unwrap(); let mut server_rooms = self.rooms.lock(); let room = server_rooms @@ -521,125 +411,49 @@ impl TestServer { .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; let client_room = room .client_rooms - .get(&identity) + .get(identity.as_ref()) .ok_or_else(|| anyhow!("not a participant in room"))?; Ok(room .audio_tracks .iter() - .map(|track| RemoteAudioTrack { - server_track: track.clone(), - room: client_room.downgrade(), + .map(|track| { + Arc::new(RemoteAudioTrack { + server_track: track.clone(), + room: Arc::downgrade(client_room), + }) }) .collect()) } } -#[cfg(not(target_os = "windows"))] -#[derive(Default, Debug)] +#[derive(Default)] struct TestServerRoom { - client_rooms: HashMap, + client_rooms: HashMap>, video_tracks: Vec>, audio_tracks: Vec>, - participant_permissions: HashMap, + participant_permissions: HashMap, } -#[cfg(not(target_os = "windows"))] #[derive(Debug)] struct TestServerVideoTrack { - sid: TrackSid, - publisher_id: ParticipantIdentity, - // frames_rx: async_broadcast::Receiver, + sid: Sid, + publisher_id: Sid, + frames_rx: async_broadcast::Receiver, } -#[cfg(not(target_os = "windows"))] #[derive(Debug)] struct TestServerAudioTrack { - sid: TrackSid, - publisher_id: ParticipantIdentity, + sid: Sid, + publisher_id: Sid, muted: AtomicBool, } +impl TestServerRoom {} + pub struct TestApiClient { url: String, } -#[derive(Clone, Debug)] -#[non_exhaustive] -pub enum RoomEvent { - ParticipantConnected(RemoteParticipant), - ParticipantDisconnected(RemoteParticipant), - LocalTrackPublished { - publication: LocalTrackPublication, - track: LocalTrack, - participant: LocalParticipant, - }, - LocalTrackUnpublished { - publication: LocalTrackPublication, - participant: LocalParticipant, - }, - TrackSubscribed { - track: RemoteTrack, - publication: RemoteTrackPublication, - participant: RemoteParticipant, - }, - TrackUnsubscribed { - track: RemoteTrack, - publication: RemoteTrackPublication, - participant: RemoteParticipant, - }, - TrackSubscriptionFailed { - participant: RemoteParticipant, - error: String, - #[cfg(not(target_os = "windows"))] - track_sid: TrackSid, - }, - TrackPublished { - publication: RemoteTrackPublication, - participant: RemoteParticipant, - }, - TrackUnpublished { - publication: RemoteTrackPublication, - participant: RemoteParticipant, - }, - TrackMuted { - participant: Participant, - publication: TrackPublication, - }, - TrackUnmuted { - participant: Participant, - publication: TrackPublication, - }, - RoomMetadataChanged { - old_metadata: String, - metadata: String, - }, - ParticipantMetadataChanged { - participant: Participant, - old_metadata: String, - metadata: String, - }, - ParticipantNameChanged { - participant: Participant, - old_name: String, - name: String, - }, - ActiveSpeakersChanged { - speakers: Vec, - }, - #[cfg(not(target_os = "windows"))] - ConnectionStateChanged(ConnectionState), - Connected { - participants_with_tracks: Vec<(RemoteParticipant, Vec)>, - }, - #[cfg(not(target_os = "windows"))] - Disconnected { - reason: DisconnectReason, - }, - Reconnecting, - Reconnected, -} - -#[cfg(not(target_os = "windows"))] #[async_trait] impl live_kit_server::api::Client for TestApiClient { fn url(&self) -> &str { @@ -660,9 +474,7 @@ impl live_kit_server::api::Client for TestApiClient { async fn remove_participant(&self, room: String, identity: String) -> Result<()> { let server = TestServer::get(&self.url)?; - server - .remove_participant(room, ParticipantIdentity(identity)) - .await?; + server.remove_participant(room, identity).await?; Ok(()) } @@ -701,125 +513,370 @@ impl live_kit_server::api::Client for TestApiClient { } struct RoomState { - url: String, - token: String, - #[cfg(not(target_os = "windows"))] - local_identity: ParticipantIdentity, - #[cfg(not(target_os = "windows"))] - connection_state: ConnectionState, - #[cfg(not(target_os = "windows"))] - paused_audio_tracks: HashSet, - updates_tx: mpsc::Sender, + connection: ( + watch::Sender, + watch::Receiver, + ), + display_sources: Vec, + paused_audio_tracks: HashSet, + updates_tx: async_broadcast::Sender, + updates_rx: async_broadcast::Receiver, } -#[derive(Clone, Debug)] -pub struct Room(Arc>); +pub struct Room(Mutex); -#[derive(Clone, Debug)] -pub(crate) struct WeakRoom(Weak>); - -#[cfg(not(target_os = "windows"))] -impl std::fmt::Debug for RoomState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Room") - .field("url", &self.url) - .field("token", &self.token) - .field("local_identity", &self.local_identity) - .field("connection_state", &self.connection_state) - .field("paused_audio_tracks", &self.paused_audio_tracks) - .finish() - } -} - -#[cfg(target_os = "windows")] -impl std::fmt::Debug for RoomState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Room") - .field("url", &self.url) - .field("token", &self.token) - .finish() - } -} - -#[cfg(not(target_os = "windows"))] impl Room { - fn downgrade(&self) -> WeakRoom { - WeakRoom(Arc::downgrade(&self.0)) - } - - pub fn connection_state(&self) -> ConnectionState { - self.0.lock().connection_state - } - - pub fn local_participant(&self) -> LocalParticipant { - let identity = self.0.lock().local_identity.clone(); - LocalParticipant { - identity, - room: self.clone(), - } - } - - pub async fn connect( - url: &str, - token: &str, - _options: RoomOptions, - ) -> Result<(Self, mpsc::Receiver)> { - let server = TestServer::get(&url)?; - let (updates_tx, updates_rx) = mpsc::channel(1024); - let this = Self(Arc::new(Mutex::new(RoomState { - local_identity: ParticipantIdentity(String::new()), - url: url.to_string(), - token: token.to_string(), - connection_state: ConnectionState::Disconnected, + pub fn new() -> Arc { + let (updates_tx, updates_rx) = async_broadcast::broadcast(128); + Arc::new(Self(Mutex::new(RoomState { + connection: watch::channel_with(ConnectionState::Disconnected), + display_sources: Default::default(), paused_audio_tracks: Default::default(), updates_tx, - }))); - - let identity = server - .join_room(token.to_string(), this.clone()) - .await - .context("room join")?; - { - let mut state = this.0.lock(); - state.local_identity = identity; - state.connection_state = ConnectionState::Connected; - } - - Ok((this, updates_rx)) + updates_rx, + }))) } - pub fn remote_participants(&self) -> HashMap { + pub fn status(&self) -> watch::Receiver { + self.0.lock().connection.1.clone() + } + + pub fn connect(self: &Arc, url: &str, token: &str) -> impl Future> { + let this = self.clone(); + let url = url.to_string(); + let token = token.to_string(); + async move { + let server = TestServer::get(&url)?; + server + .join_room(token.clone(), this.clone()) + .await + .context("room join")?; + *this.0.lock().connection.0.borrow_mut() = ConnectionState::Connected { url, token }; + Ok(()) + } + } + + pub fn display_sources(self: &Arc) -> impl Future>> { + let this = self.clone(); + async move { + // todo(linux): Remove this once the cross-platform LiveKit implementation is merged + #[cfg(any(test, feature = "test-support"))] + { + let server = this.test_server(); + server.executor.simulate_random_delay().await; + } + + Ok(this.0.lock().display_sources.clone()) + } + } + + pub fn publish_video_track( + self: &Arc, + track: LocalVideoTrack, + ) -> impl Future> { + let this = self.clone(); + let track = track.clone(); + async move { + let sid = this + .test_server() + .publish_video_track(this.token(), track) + .await?; + Ok(LocalTrackPublication { + room: Arc::downgrade(&this), + sid, + }) + } + } + + pub fn publish_audio_track( + self: &Arc, + track: LocalAudioTrack, + ) -> impl Future> { + let this = self.clone(); + let track = track.clone(); + async move { + let sid = this + .test_server() + .publish_audio_track(this.token(), &track) + .await?; + Ok(LocalTrackPublication { + room: Arc::downgrade(&this), + sid, + }) + } + } + + pub fn unpublish_track(&self, _publication: LocalTrackPublication) {} + + pub fn remote_audio_tracks(&self, publisher_id: &str) -> Vec> { + if !self.is_connected() { + return Vec::new(); + } + self.test_server() - .remote_participants(self.0.lock().token.clone()) + .audio_tracks(self.token()) .unwrap() + .into_iter() + .filter(|track| track.publisher_id() == publisher_id) + .collect() + } + + pub fn remote_audio_track_publications( + &self, + publisher_id: &str, + ) -> Vec> { + if !self.is_connected() { + return Vec::new(); + } + + self.test_server() + .audio_tracks(self.token()) + .unwrap() + .into_iter() + .filter(|track| track.publisher_id() == publisher_id) + .map(|_track| Arc::new(RemoteTrackPublication {})) + .collect() + } + + pub fn remote_video_tracks(&self, publisher_id: &str) -> Vec> { + if !self.is_connected() { + return Vec::new(); + } + + self.test_server() + .video_tracks(self.token()) + .unwrap() + .into_iter() + .filter(|track| track.publisher_id() == publisher_id) + .collect() + } + + pub fn updates(&self) -> impl Stream { + self.0.lock().updates_rx.clone() + } + + pub fn set_display_sources(&self, sources: Vec) { + self.0.lock().display_sources = sources; } fn test_server(&self) -> Arc { - TestServer::get(&self.0.lock().url).unwrap() + match self.0.lock().connection.1.borrow().clone() { + ConnectionState::Disconnected => panic!("must be connected to call this method"), + ConnectionState::Connected { url, .. } => TestServer::get(&url).unwrap(), + } } fn token(&self) -> String { - self.0.lock().token.clone() + match self.0.lock().connection.1.borrow().clone() { + ConnectionState::Disconnected => panic!("must be connected to call this method"), + ConnectionState::Connected { token, .. } => token, + } + } + + fn is_connected(&self) -> bool { + match *self.0.lock().connection.1.borrow() { + ConnectionState::Disconnected => false, + ConnectionState::Connected { .. } => true, + } } } -#[cfg(not(target_os = "windows"))] -impl Drop for RoomState { +impl Drop for Room { fn drop(&mut self) { - if self.connection_state == ConnectionState::Connected { - if let Ok(server) = TestServer::get(&self.url) { + if let ConnectionState::Connected { token, .. } = mem::replace( + &mut *self.0.lock().connection.0.borrow_mut(), + ConnectionState::Disconnected, + ) { + if let Ok(server) = TestServer::get(&token) { let executor = server.executor.clone(); - let token = self.token.clone(); executor - .spawn(async move { server.leave_room(token).await.ok() }) + .spawn(async move { server.leave_room(token).await.unwrap() }) .detach(); } } } } -impl WeakRoom { - fn upgrade(&self) -> Option { - self.0.upgrade().map(Room) +#[derive(Clone)] +pub struct LocalTrackPublication { + sid: String, + room: Weak, +} + +impl LocalTrackPublication { + pub fn set_mute(&self, mute: bool) -> impl Future> { + let sid = self.sid.clone(); + let room = self.room.clone(); + async move { + if let Some(room) = room.upgrade() { + room.test_server() + .set_track_muted(&room.token(), &sid, mute) + } else { + Err(anyhow!("no such room")) + } + } + } + + pub fn is_muted(&self) -> bool { + if let Some(room) = self.room.upgrade() { + room.test_server() + .is_track_muted(&room.token(), &self.sid) + .unwrap_or(false) + } else { + false + } + } + + pub fn sid(&self) -> String { + self.sid.clone() + } +} + +pub struct RemoteTrackPublication; + +impl RemoteTrackPublication { + pub fn set_enabled(&self, _enabled: bool) -> impl Future> { + async { Ok(()) } + } + + pub fn is_muted(&self) -> bool { + false + } + + pub fn sid(&self) -> String { + "".to_string() + } +} + +#[derive(Clone)] +pub struct LocalVideoTrack { + frames_rx: async_broadcast::Receiver, +} + +impl LocalVideoTrack { + pub fn screen_share_for_display(display: &MacOSDisplay) -> Self { + Self { + frames_rx: display.frames.1.clone(), + } + } +} + +#[derive(Clone)] +pub struct LocalAudioTrack; + +impl LocalAudioTrack { + pub fn create() -> Self { + Self + } +} + +#[derive(Debug)] +pub struct RemoteVideoTrack { + server_track: Arc, +} + +impl RemoteVideoTrack { + pub fn sid(&self) -> &str { + &self.server_track.sid + } + + pub fn publisher_id(&self) -> &str { + &self.server_track.publisher_id + } + + pub fn frames(&self) -> async_broadcast::Receiver { + self.server_track.frames_rx.clone() + } +} + +#[derive(Debug)] +pub struct RemoteAudioTrack { + server_track: Arc, + room: Weak, +} + +impl RemoteAudioTrack { + pub fn sid(&self) -> &str { + &self.server_track.sid + } + + pub fn publisher_id(&self) -> &str { + &self.server_track.publisher_id + } + + pub fn start(&self) { + if let Some(room) = self.room.upgrade() { + room.0 + .lock() + .paused_audio_tracks + .remove(&self.server_track.sid); + } + } + + pub fn stop(&self) { + if let Some(room) = self.room.upgrade() { + room.0 + .lock() + .paused_audio_tracks + .insert(self.server_track.sid.clone()); + } + } + + pub fn is_playing(&self) -> bool { + !self + .room + .upgrade() + .unwrap() + .0 + .lock() + .paused_audio_tracks + .contains(&self.server_track.sid) + } +} + +#[derive(Clone)] +pub struct MacOSDisplay { + frames: ( + async_broadcast::Sender, + async_broadcast::Receiver, + ), +} + +impl Default for MacOSDisplay { + fn default() -> Self { + Self::new() + } +} + +impl MacOSDisplay { + pub fn new() -> Self { + Self { + frames: async_broadcast::broadcast(128), + } + } + + pub fn send_frame(&self, frame: Frame) { + self.frames.0.try_broadcast(frame).unwrap(); + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Frame { + pub label: String, + pub width: usize, + pub height: usize, +} + +impl Frame { + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } + + pub fn image(&self) -> SurfaceSource { + unimplemented!("you can't call this in test mode") } } diff --git a/crates/live_kit_client/src/test/participant.rs b/crates/live_kit_client/src/test/participant.rs deleted file mode 100644 index 8d476b1537..0000000000 --- a/crates/live_kit_client/src/test/participant.rs +++ /dev/null @@ -1,111 +0,0 @@ -use super::*; - -#[derive(Clone, Debug)] -pub enum Participant { - Local(LocalParticipant), - Remote(RemoteParticipant), -} - -#[derive(Clone, Debug)] -pub struct LocalParticipant { - #[cfg(not(target_os = "windows"))] - pub(super) identity: ParticipantIdentity, - pub(super) room: Room, -} - -#[derive(Clone, Debug)] -pub struct RemoteParticipant { - #[cfg(not(target_os = "windows"))] - pub(super) identity: ParticipantIdentity, - pub(super) room: WeakRoom, -} - -#[cfg(not(target_os = "windows"))] -impl Participant { - pub fn identity(&self) -> ParticipantIdentity { - match self { - Participant::Local(participant) => participant.identity.clone(), - Participant::Remote(participant) => participant.identity.clone(), - } - } -} - -#[cfg(not(target_os = "windows"))] -impl LocalParticipant { - pub async fn unpublish_track(&self, track: &TrackSid) -> Result<()> { - self.room - .test_server() - .unpublish_track(self.room.token(), track) - .await - } - - pub async fn publish_track( - &self, - track: LocalTrack, - _options: TrackPublishOptions, - ) -> Result { - let this = self.clone(); - let track = track.clone(); - let server = this.room.test_server(); - let sid = match track { - LocalTrack::Video(track) => { - server.publish_video_track(this.room.token(), track).await? - } - LocalTrack::Audio(track) => { - server - .publish_audio_track(this.room.token(), &track) - .await? - } - }; - Ok(LocalTrackPublication { - room: self.room.downgrade(), - sid, - }) - } -} - -#[cfg(not(target_os = "windows"))] -impl RemoteParticipant { - pub fn track_publications(&self) -> HashMap { - if let Some(room) = self.room.upgrade() { - let server = room.test_server(); - let audio = server - .audio_tracks(room.token()) - .unwrap() - .into_iter() - .filter(|track| track.publisher_id() == self.identity) - .map(|track| { - ( - track.sid(), - RemoteTrackPublication { - sid: track.sid(), - room: self.room.clone(), - track: RemoteTrack::Audio(track), - }, - ) - }); - let video = server - .video_tracks(room.token()) - .unwrap() - .into_iter() - .filter(|track| track.publisher_id() == self.identity) - .map(|track| { - ( - track.sid(), - RemoteTrackPublication { - sid: track.sid(), - room: self.room.clone(), - track: RemoteTrack::Video(track), - }, - ) - }); - audio.chain(video).collect() - } else { - HashMap::default() - } - } - - pub fn identity(&self) -> ParticipantIdentity { - self.identity.clone() - } -} diff --git a/crates/live_kit_client/src/test/publication.rs b/crates/live_kit_client/src/test/publication.rs deleted file mode 100644 index 6a3dfa0a51..0000000000 --- a/crates/live_kit_client/src/test/publication.rs +++ /dev/null @@ -1,116 +0,0 @@ -use super::*; - -#[derive(Clone, Debug)] -pub enum TrackPublication { - Local(LocalTrackPublication), - Remote(RemoteTrackPublication), -} - -#[derive(Clone, Debug)] -pub struct LocalTrackPublication { - #[cfg(not(target_os = "windows"))] - pub(crate) sid: TrackSid, - pub(crate) room: WeakRoom, -} - -#[derive(Clone, Debug)] -pub struct RemoteTrackPublication { - #[cfg(not(target_os = "windows"))] - pub(crate) sid: TrackSid, - pub(crate) room: WeakRoom, - pub(crate) track: RemoteTrack, -} - -#[cfg(not(target_os = "windows"))] -impl TrackPublication { - pub fn sid(&self) -> TrackSid { - match self { - TrackPublication::Local(track) => track.sid(), - TrackPublication::Remote(track) => track.sid(), - } - } - - pub fn is_muted(&self) -> bool { - match self { - TrackPublication::Local(track) => track.is_muted(), - TrackPublication::Remote(track) => track.is_muted(), - } - } -} - -#[cfg(not(target_os = "windows"))] -impl LocalTrackPublication { - pub fn sid(&self) -> TrackSid { - self.sid.clone() - } - - pub fn mute(&self) { - self.set_mute(true) - } - - pub fn unmute(&self) { - self.set_mute(false) - } - - fn set_mute(&self, mute: bool) { - if let Some(room) = self.room.upgrade() { - room.test_server() - .set_track_muted(&room.token(), &self.sid, mute) - .ok(); - } - } - - pub fn is_muted(&self) -> bool { - if let Some(room) = self.room.upgrade() { - room.test_server() - .is_track_muted(&room.token(), &self.sid) - .unwrap_or(false) - } else { - false - } - } -} - -#[cfg(not(target_os = "windows"))] -impl RemoteTrackPublication { - pub fn sid(&self) -> TrackSid { - self.sid.clone() - } - - pub fn track(&self) -> Option { - Some(self.track.clone()) - } - - pub fn kind(&self) -> TrackKind { - self.track.kind() - } - - pub fn is_muted(&self) -> bool { - if let Some(room) = self.room.upgrade() { - room.test_server() - .is_track_muted(&room.token(), &self.sid) - .unwrap_or(false) - } else { - false - } - } - - pub fn is_enabled(&self) -> bool { - if let Some(room) = self.room.upgrade() { - !room.0.lock().paused_audio_tracks.contains(&self.sid) - } else { - false - } - } - - pub fn set_enabled(&self, enabled: bool) { - if let Some(room) = self.room.upgrade() { - let paused_audio_tracks = &mut room.0.lock().paused_audio_tracks; - if enabled { - paused_audio_tracks.remove(&self.sid); - } else { - paused_audio_tracks.insert(self.sid.clone()); - } - } - } -} diff --git a/crates/live_kit_client/src/test/track.rs b/crates/live_kit_client/src/test/track.rs deleted file mode 100644 index 302177a10a..0000000000 --- a/crates/live_kit_client/src/test/track.rs +++ /dev/null @@ -1,201 +0,0 @@ -use super::*; -#[cfg(not(windows))] -use webrtc::{audio_source::RtcAudioSource, video_source::RtcVideoSource}; - -#[cfg(not(windows))] -pub use livekit::track::{TrackKind, TrackSource}; - -#[derive(Clone, Debug)] -pub enum LocalTrack { - Audio(LocalAudioTrack), - Video(LocalVideoTrack), -} - -#[derive(Clone, Debug)] -pub enum RemoteTrack { - Audio(RemoteAudioTrack), - Video(RemoteVideoTrack), -} - -#[derive(Clone, Debug)] -pub struct LocalVideoTrack {} - -#[derive(Clone, Debug)] -pub struct LocalAudioTrack {} - -#[derive(Clone, Debug)] -pub struct RemoteVideoTrack { - #[cfg(not(target_os = "windows"))] - pub(super) server_track: Arc, - pub(super) _room: WeakRoom, -} - -#[derive(Clone, Debug)] -pub struct RemoteAudioTrack { - #[cfg(not(target_os = "windows"))] - pub(super) server_track: Arc, - pub(super) room: WeakRoom, -} - -pub enum RtcTrack { - Audio(RtcAudioTrack), - Video(RtcVideoTrack), -} - -pub struct RtcAudioTrack { - #[cfg(not(target_os = "windows"))] - pub(super) server_track: Arc, - pub(super) room: WeakRoom, -} - -pub struct RtcVideoTrack { - #[cfg(not(target_os = "windows"))] - pub(super) _server_track: Arc, -} - -#[cfg(not(target_os = "windows"))] -impl RemoteTrack { - pub fn sid(&self) -> TrackSid { - match self { - RemoteTrack::Audio(track) => track.sid(), - RemoteTrack::Video(track) => track.sid(), - } - } - - pub fn kind(&self) -> TrackKind { - match self { - RemoteTrack::Audio(_) => TrackKind::Audio, - RemoteTrack::Video(_) => TrackKind::Video, - } - } - - pub fn publisher_id(&self) -> ParticipantIdentity { - match self { - RemoteTrack::Audio(track) => track.publisher_id(), - RemoteTrack::Video(track) => track.publisher_id(), - } - } - - pub fn rtc_track(&self) -> RtcTrack { - match self { - RemoteTrack::Audio(track) => RtcTrack::Audio(track.rtc_track()), - RemoteTrack::Video(track) => RtcTrack::Video(track.rtc_track()), - } - } -} - -#[cfg(not(windows))] -impl LocalVideoTrack { - pub fn create_video_track(_name: &str, _source: RtcVideoSource) -> Self { - Self {} - } -} - -#[cfg(not(windows))] -impl LocalAudioTrack { - pub fn create_audio_track(_name: &str, _source: RtcAudioSource) -> Self { - Self {} - } -} - -#[cfg(not(target_os = "windows"))] -impl RemoteAudioTrack { - pub fn sid(&self) -> TrackSid { - self.server_track.sid.clone() - } - - pub fn publisher_id(&self) -> ParticipantIdentity { - self.server_track.publisher_id.clone() - } - - pub fn start(&self) { - if let Some(room) = self.room.upgrade() { - room.0 - .lock() - .paused_audio_tracks - .remove(&self.server_track.sid); - } - } - - pub fn stop(&self) { - if let Some(room) = self.room.upgrade() { - room.0 - .lock() - .paused_audio_tracks - .insert(self.server_track.sid.clone()); - } - } - - pub fn rtc_track(&self) -> RtcAudioTrack { - RtcAudioTrack { - server_track: self.server_track.clone(), - room: self.room.clone(), - } - } -} - -#[cfg(not(target_os = "windows"))] -impl RemoteVideoTrack { - pub fn sid(&self) -> TrackSid { - self.server_track.sid.clone() - } - - pub fn publisher_id(&self) -> ParticipantIdentity { - self.server_track.publisher_id.clone() - } - - pub fn rtc_track(&self) -> RtcVideoTrack { - RtcVideoTrack { - _server_track: self.server_track.clone(), - } - } -} - -#[cfg(not(target_os = "windows"))] -impl RtcTrack { - pub fn enabled(&self) -> bool { - match self { - RtcTrack::Audio(track) => track.enabled(), - RtcTrack::Video(track) => track.enabled(), - } - } - - pub fn set_enabled(&self, enabled: bool) { - match self { - RtcTrack::Audio(track) => track.set_enabled(enabled), - RtcTrack::Video(_) => {} - } - } -} - -#[cfg(not(target_os = "windows"))] -impl RtcAudioTrack { - pub fn set_enabled(&self, enabled: bool) { - if let Some(room) = self.room.upgrade() { - let paused_audio_tracks = &mut room.0.lock().paused_audio_tracks; - if enabled { - paused_audio_tracks.remove(&self.server_track.sid); - } else { - paused_audio_tracks.insert(self.server_track.sid.clone()); - } - } - } - - pub fn enabled(&self) -> bool { - if let Some(room) = self.room.upgrade() { - !room - .0 - .lock() - .paused_audio_tracks - .contains(&self.server_track.sid) - } else { - false - } - } -} - -impl RtcVideoTrack { - pub fn enabled(&self) -> bool { - true - } -} diff --git a/crates/live_kit_client/src/test/webrtc.rs b/crates/live_kit_client/src/test/webrtc.rs deleted file mode 100644 index 6ac06e0484..0000000000 --- a/crates/live_kit_client/src/test/webrtc.rs +++ /dev/null @@ -1,136 +0,0 @@ -use super::track::{RtcAudioTrack, RtcVideoTrack}; -use futures::Stream; -use livekit::webrtc as real; -use std::{ - pin::Pin, - task::{Context, Poll}, -}; - -pub mod video_stream { - use super::*; - - pub mod native { - use super::*; - use real::video_frame::BoxVideoFrame; - - pub struct NativeVideoStream { - pub track: RtcVideoTrack, - } - - impl NativeVideoStream { - pub fn new(track: RtcVideoTrack) -> Self { - Self { track } - } - } - - impl Stream for NativeVideoStream { - type Item = BoxVideoFrame; - - fn poll_next(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { - Poll::Pending - } - } - } -} - -pub mod audio_stream { - use super::*; - - pub mod native { - use super::*; - use real::audio_frame::AudioFrame; - - pub struct NativeAudioStream { - pub track: RtcAudioTrack, - } - - impl NativeAudioStream { - pub fn new(track: RtcAudioTrack, _sample_rate: i32, _num_channels: i32) -> Self { - Self { track } - } - } - - impl Stream for NativeAudioStream { - type Item = AudioFrame<'static>; - - fn poll_next(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { - Poll::Pending - } - } - } -} - -pub mod audio_source { - use super::*; - - pub use real::audio_source::AudioSourceOptions; - - pub mod native { - use std::sync::Arc; - - use super::*; - use real::{audio_frame::AudioFrame, RtcError}; - - #[derive(Clone)] - pub struct NativeAudioSource { - pub options: Arc, - pub sample_rate: u32, - pub num_channels: u32, - } - - impl NativeAudioSource { - pub fn new( - options: AudioSourceOptions, - sample_rate: u32, - num_channels: u32, - _queue_size_ms: u32, - ) -> Self { - Self { - options: Arc::new(options), - sample_rate, - num_channels, - } - } - - pub async fn capture_frame(&self, _frame: &AudioFrame<'_>) -> Result<(), RtcError> { - Ok(()) - } - } - } - - pub enum RtcAudioSource { - Native(native::NativeAudioSource), - } -} - -pub use livekit::webrtc::audio_frame; -pub use livekit::webrtc::video_frame; - -pub mod video_source { - use super::*; - pub use real::video_source::VideoResolution; - - pub struct RTCVideoSource; - - pub mod native { - use super::*; - use real::video_frame::{VideoBuffer, VideoFrame}; - - #[derive(Clone)] - pub struct NativeVideoSource { - pub resolution: VideoResolution, - } - - impl NativeVideoSource { - pub fn new(resolution: super::VideoResolution) -> Self { - Self { resolution } - } - - pub fn capture_frame>(&self, _frame: &VideoFrame) {} - } - } - - pub enum RtcVideoSource { - Native(native::NativeVideoSource), - } -} diff --git a/crates/media/Cargo.toml b/crates/media/Cargo.toml index 70478eeb75..92940d1c52 100644 --- a/crates/media/Cargo.toml +++ b/crates/media/Cargo.toml @@ -17,7 +17,6 @@ anyhow.workspace = true [target.'cfg(target_os = "macos")'.dependencies] core-foundation.workspace = true -ctor.workspace = true foreign-types = "0.5" metal = "0.29" objc = "0.2" diff --git a/crates/media/src/media.rs b/crates/media/src/media.rs index 3f55475589..8757249c31 100644 --- a/crates/media/src/media.rs +++ b/crates/media/src/media.rs @@ -253,14 +253,11 @@ pub mod core_media { } } - pub fn image_buffer(&self) -> Option { + pub fn image_buffer(&self) -> CVImageBuffer { unsafe { - let ptr = CMSampleBufferGetImageBuffer(self.as_concrete_TypeRef()); - if ptr.is_null() { - None - } else { - Some(CVImageBuffer::wrap_under_get_rule(ptr)) - } + CVImageBuffer::wrap_under_get_rule(CMSampleBufferGetImageBuffer( + self.as_concrete_TypeRef(), + )) } } diff --git a/crates/title_bar/src/collab.rs b/crates/title_bar/src/collab.rs index f043194a03..805c0e7202 100644 --- a/crates/title_bar/src/collab.rs +++ b/crates/title_bar/src/collab.rs @@ -296,9 +296,9 @@ impl TitleBar { let is_muted = room.is_muted(); let is_deafened = room.is_deafened().unwrap_or(false); let is_screen_sharing = room.is_screen_sharing(); - let can_use_microphone = room.can_use_microphone(cx); + let can_use_microphone = room.can_use_microphone(); let can_share_projects = room.can_share_projects(); - let screen_sharing_supported = match self.platform_style { + let platform_supported = match self.platform_style { PlatformStyle::Mac => true, PlatformStyle::Linux | PlatformStyle::Windows => false, }; @@ -365,7 +365,9 @@ impl TitleBar { ) .tooltip(move |cx| { Tooltip::text( - if is_muted { + if !platform_supported { + "Cannot share microphone" + } else if is_muted { "Unmute microphone" } else { "Mute microphone" @@ -375,45 +377,56 @@ impl TitleBar { }) .style(ButtonStyle::Subtle) .icon_size(IconSize::Small) - .selected(is_muted) + .selected(platform_supported && is_muted) + .disabled(!platform_supported) .selected_style(ButtonStyle::Tinted(TintColor::Negative)) .on_click(move |_, cx| { toggle_mute(&Default::default(), cx); }) .into_any_element(), ); - - children.push( - IconButton::new( - "mute-sound", - if is_deafened { - ui::IconName::AudioOff - } else { - ui::IconName::AudioOn - }, - ) - .style(ButtonStyle::Subtle) - .selected_style(ButtonStyle::Tinted(TintColor::Negative)) - .icon_size(IconSize::Small) - .selected(is_deafened) - .tooltip(move |cx| { - Tooltip::with_meta("Deafen Audio", None, "Mic will be muted", cx) - }) - .on_click(move |_, cx| toggle_deafen(&Default::default(), cx)) - .into_any_element(), - ); } - if screen_sharing_supported { + children.push( + IconButton::new( + "mute-sound", + if is_deafened { + ui::IconName::AudioOff + } else { + ui::IconName::AudioOn + }, + ) + .style(ButtonStyle::Subtle) + .selected_style(ButtonStyle::Tinted(TintColor::Negative)) + .icon_size(IconSize::Small) + .selected(is_deafened) + .disabled(!platform_supported) + .tooltip(move |cx| { + if !platform_supported { + Tooltip::text("Cannot share microphone", cx) + } else if can_use_microphone { + Tooltip::with_meta("Deafen Audio", None, "Mic will be muted", cx) + } else { + Tooltip::text("Deafen Audio", cx) + } + }) + .on_click(move |_, cx| toggle_deafen(&Default::default(), cx)) + .into_any_element(), + ); + + if can_share_projects { children.push( IconButton::new("screen-share", ui::IconName::Screen) .style(ButtonStyle::Subtle) .icon_size(IconSize::Small) .selected(is_screen_sharing) + .disabled(!platform_supported) .selected_style(ButtonStyle::Tinted(TintColor::Accent)) .tooltip(move |cx| { Tooltip::text( - if is_screen_sharing { + if !platform_supported { + "Cannot share screen" + } else if is_screen_sharing { "Stop Sharing Screen" } else { "Share Screen" diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index 285946cce0..59df859488 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -2,13 +2,16 @@ use crate::{ item::{Item, ItemEvent}, ItemNavHistory, WorkspaceId, }; -use call::{RemoteVideoTrack, RemoteVideoTrackView}; +use anyhow::Result; +use call::participant::{Frame, RemoteVideoTrack}; use client::{proto::PeerId, User}; +use futures::StreamExt; use gpui::{ - div, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement, ParentElement, - Render, SharedString, Styled, View, ViewContext, VisualContext, WindowContext, + div, surface, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement, + ParentElement, Render, SharedString, Styled, Task, View, ViewContext, VisualContext, + WindowContext, }; -use std::sync::Arc; +use std::sync::{Arc, Weak}; use ui::{prelude::*, Icon, IconName}; pub enum Event { @@ -16,30 +19,40 @@ pub enum Event { } pub struct SharedScreen { + track: Weak, + frame: Option, pub peer_id: PeerId, user: Arc, nav_history: Option, - view: View, + _maintain_frame: Task>, focus: FocusHandle, } impl SharedScreen { pub fn new( - track: RemoteVideoTrack, + track: &Arc, peer_id: PeerId, user: Arc, cx: &mut ViewContext, ) -> Self { - let view = cx.new_view(|cx| RemoteVideoTrackView::new(track.clone(), cx)); - cx.subscribe(&view, |_, _, ev, cx| match ev { - call::RemoteVideoTrackViewEvent::Close => cx.emit(Event::Close), - }) - .detach(); + cx.focus_handle(); + let mut frames = track.frames(); Self { - view, + track: Arc::downgrade(track), + frame: None, peer_id, user, nav_history: Default::default(), + _maintain_frame: cx.spawn(|this, mut cx| async move { + while let Some(frame) = frames.next().await { + this.update(&mut cx, |this, cx| { + this.frame = Some(frame); + cx.notify(); + })?; + } + this.update(&mut cx, |_, cx| cx.emit(Event::Close))?; + Ok(()) + }), focus: cx.focus_handle(), } } @@ -59,7 +72,11 @@ impl Render for SharedScreen { .track_focus(&self.focus) .key_context("SharedScreen") .size_full() - .child(self.view.clone()) + .children( + self.frame + .as_ref() + .map(|frame| surface(frame.image()).size_full()), + ) } } @@ -97,13 +114,8 @@ impl Item for SharedScreen { _workspace_id: Option, cx: &mut ViewContext, ) -> Option> { - Some(cx.new_view(|cx| Self { - view: self.view.update(cx, |view, cx| view.clone(cx)), - peer_id: self.peer_id, - user: self.user.clone(), - nav_history: Default::default(), - focus: cx.focus_handle(), - })) + let track = self.track.upgrade()?; + Some(cx.new_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx))) } fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 31089f7882..833a8b15a0 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3939,17 +3939,6 @@ impl Workspace { None } - #[cfg(target_os = "windows")] - fn shared_screen_for_peer( - &self, - _peer_id: PeerId, - _pane: &View, - _cx: &mut WindowContext, - ) -> Option> { - None - } - - #[cfg(not(target_os = "windows"))] fn shared_screen_for_peer( &self, peer_id: PeerId, @@ -3968,7 +3957,7 @@ impl Workspace { } } - Some(cx.new_view(|cx| SharedScreen::new(track, peer_id, user.clone(), cx))) + Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx))) } pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext) { From 9260abafbaa3eddb3922da3555d6dbe9903b05d0 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Mon, 18 Nov 2024 05:38:31 -0700 Subject: [PATCH 022/157] Use `HashMap` instead of `HashSet` in outline_panel (#20780) Came across this because I noticed that `Entry` implements `Hash`, which was surprising to me. I believe that `ProjectEntryId` should be unique and so it seems better to dedupe based on this. Release Notes: - N/A --- crates/outline_panel/src/outline_panel.rs | 16 ++++++++++------ crates/worktree/src/worktree.rs | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index a6d1903282..f878b582d9 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -2178,8 +2178,10 @@ impl OutlinePanel { .background_executor() .spawn(async move { let mut processed_external_buffers = HashSet::default(); - let mut new_worktree_entries = - HashMap::)>::default(); + let mut new_worktree_entries = HashMap::< + WorktreeId, + (worktree::Snapshot, HashMap), + >::default(); let mut worktree_excerpts = HashMap::< WorktreeId, HashMap)>, @@ -2213,7 +2215,7 @@ impl OutlinePanel { entry.path.as_ref(), ); - let mut entries_to_add = HashSet::default(); + let mut entries_to_add = HashMap::default(); worktree_excerpts .entry(worktree_id) .or_default() @@ -2238,7 +2240,9 @@ impl OutlinePanel { } } - let new_entry_added = entries_to_add.insert(current_entry); + let new_entry_added = entries_to_add + .insert(current_entry.id, current_entry) + .is_none(); if new_entry_added && traversal.back_to_parent() { if let Some(parent_entry) = traversal.entry() { current_entry = parent_entry.clone(); @@ -2249,7 +2253,7 @@ impl OutlinePanel { } new_worktree_entries .entry(worktree_id) - .or_insert_with(|| (worktree.clone(), HashSet::default())) + .or_insert_with(|| (worktree.clone(), HashMap::default())) .1 .extend(entries_to_add); } @@ -2276,7 +2280,7 @@ impl OutlinePanel { let worktree_entries = new_worktree_entries .into_iter() .map(|(worktree_id, (worktree_snapshot, entries))| { - let mut entries = entries.into_iter().collect::>(); + let mut entries = entries.into_values().collect::>(); // For a proper git status propagation, we have to keep the entries sorted lexicographically. entries.sort_by(|a, b| a.path.as_ref().cmp(b.path.as_ref())); worktree_snapshot.propagate_git_statuses(&mut entries); diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 28b23d2fa7..a3e290e6d6 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -3344,7 +3344,7 @@ impl File { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Entry { pub id: ProjectEntryId, pub kind: EntryKind, @@ -3376,7 +3376,7 @@ pub struct Entry { pub is_fifo: bool, } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum EntryKind { UnloadedDir, PendingDir, From 5fd7afb9da3ff54ea1841a99e7c2d32b4beae994 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Mon, 18 Nov 2024 14:23:29 +0000 Subject: [PATCH 023/157] docs: More language extension config.toml key documentation (#20818) Release Notes: - N/A --- docs/src/extensions/languages.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/src/extensions/languages.md b/docs/src/extensions/languages.md index b7e0cb4482..e4fc1aca2b 100644 --- a/docs/src/extensions/languages.md +++ b/docs/src/extensions/languages.md @@ -20,22 +20,22 @@ path_suffixes = ["myl"] line_comments = ["# "] ``` -- `name` is the human readable name that will show up in the Select Language dropdown. -- `grammar` is the name of a grammar. Grammars are registered separately, described below. -- `path_suffixes` (optional) is an array of file suffixes that should be associated with this language. This supports glob patterns like `config/**/*.toml` where `**` matches 0 or more directories and `*` matches 0 or more characters. -- `line_comments` (optional) is an array of strings that are used to identify line comments in the language. +- `name` (required) is the human readable name that will show up in the Select Language dropdown. +- `grammar` (required) is the name of a grammar. Grammars are registered separately, described below. +- `path_suffixes` is an array of file suffixes that should be associated with this language. Unlike `file_types` in settings, this does not support glob patterns. +- `line_comments` is an array of strings that are used to identify line comments in the language. This is used for the `editor::ToggleComments` keybind: `{#kb editor::ToggleComments}` for toggling lines of code. +- `tab_size` defines the indentation/tab size used for this language (default is `4`). +- `hard_tabs` whether to indent with tabs (`true`) or spaces (`false`, the default). +- `first_line_pattern` is a regular expression, that in addition to `path_suffixes` (above) or `file_types` in settings can be used to match files which should use this language. For example Zed uses this to identify Shell Scripts by matching the [shebangs lines](https://github.com/zed-industries/zed/blob/main/crates/languages/src/bash/config.toml) in the first line of a script. ## Grammar From d265e44209f120b0c4804aef6b3f3ce047a01ddf Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Mon, 18 Nov 2024 10:55:44 -0800 Subject: [PATCH 031/157] Don't treat absence of a file on fs as conflict for new files from CLI (#20828) Closes #20827 Release Notes: - Fixes bug where save for new files created via CLI would report a conflict and ask about overwriting. --- crates/language/src/buffer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 571e444d7c..d1a01c26e6 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1775,11 +1775,12 @@ impl Buffer { return false; }; match file.disk_state() { - DiskState::New | DiskState::Deleted => true, + DiskState::New => false, DiskState::Present { mtime } => match self.saved_mtime { Some(saved_mtime) => mtime > saved_mtime && self.has_unsaved_edits(), None => true, }, + DiskState::Deleted => true, } } From 37899187c630a629d3e2c972c4e1316f7d3a0195 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:32:16 -0300 Subject: [PATCH 032/157] Adjust file finder width configuration (#20819) Follow up to: https://github.com/zed-industries/zed/pull/18682 This PR tweaks the setting value, so it's clear we're referring to `max-width`, meaning the width will change up to a specific value depending on the available window size. Then, it also makes `Small` the default value, which, in practice, makes the modal size the same as it was before the original PR linked above. Release Notes: - N/A --------- Co-authored-by: Kirill Bulatov --- assets/settings/default.json | 28 +++++++------ crates/file_finder/src/file_finder.rs | 25 ++++++++--- .../file_finder/src/file_finder_settings.rs | 42 +++---------------- docs/src/configuring-zed.md | 8 ++-- 4 files changed, 44 insertions(+), 59 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index c881c61d35..2352e75ee9 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -581,20 +581,22 @@ "file_finder": { // Whether to show file icons in the file finder. "file_icons": true, - // Width of the file finder modal. This setting can - // take four values. + // Determines how much space the file finder can take up in relation to the available window width. + // There are 5 possible width values: // - // 1. Small width: - // "modal_width": "small", - // 2. Medium width (default): - // "modal_width": "medium", - // 3. Large width: - // "modal_width": "large", - // 4. Extra Large width: - // "modal_width": "xlarge" - // 5. Fullscreen width: - // "modal_width": "full" - "modal_width": "medium" + // 1. Small: This value is essentially a fixed width. + // "modal_width": "small" + // 2. Medium: + // "modal_width": "medium" + // 3. Large: + // "modal_width": "large" + // 4. Extra Large: + // "modal_width": "xlarge" + // 5. Fullscreen: This value removes any horizontal padding, as it consumes the whole viewport width. + // "modal_width": "full" + // + // Default: small + "modal_max_width": "small" }, // Whether or not to remove any trailing whitespace from lines of a buffer // before saving it. diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index f9c058de23..138a02d1f6 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -10,7 +10,7 @@ pub use open_path_prompt::OpenPathDelegate; use collections::HashMap; use editor::{scroll::Autoscroll, Bias, Editor}; -use file_finder_settings::FileFinderSettings; +use file_finder_settings::{FileFinderSettings, FileFinderWidth}; use file_icons::FileIcons; use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; use gpui::{ @@ -244,6 +244,22 @@ impl FileFinder { } }) } + + pub fn modal_max_width( + width_setting: Option, + cx: &mut ViewContext, + ) -> Pixels { + let window_width = cx.viewport_size().width; + let small_width = Pixels(545.); + + match width_setting { + None | Some(FileFinderWidth::Small) => small_width, + Some(FileFinderWidth::Full) => window_width, + Some(FileFinderWidth::XLarge) => (window_width - Pixels(512.)).max(small_width), + Some(FileFinderWidth::Large) => (window_width - Pixels(768.)).max(small_width), + Some(FileFinderWidth::Medium) => (window_width - Pixels(1024.)).max(small_width), + } + } } impl EventEmitter for FileFinder {} @@ -258,13 +274,12 @@ impl Render for FileFinder { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let key_context = self.picker.read(cx).delegate.key_context(cx); - let window_max_width: Pixels = cx.viewport_size().width; - let modal_choice = FileFinderSettings::get_global(cx).modal_width; - let width = modal_choice.calc_width(window_max_width); + let file_finder_settings = FileFinderSettings::get_global(cx); + let modal_max_width = Self::modal_max_width(file_finder_settings.modal_max_width, cx); v_flex() .key_context(key_context) - .w(width) + .w(modal_max_width) .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed)) .on_action(cx.listener(Self::handle_select_prev)) .on_action(cx.listener(Self::handle_open_menu)) diff --git a/crates/file_finder/src/file_finder_settings.rs b/crates/file_finder/src/file_finder_settings.rs index 4379c8f543..0512021d87 100644 --- a/crates/file_finder/src/file_finder_settings.rs +++ b/crates/file_finder/src/file_finder_settings.rs @@ -2,13 +2,11 @@ use anyhow::Result; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; -use std::cmp; -use ui::Pixels; #[derive(Deserialize, Debug, Clone, Copy, PartialEq)] pub struct FileFinderSettings { pub file_icons: bool, - pub modal_width: FileFinderWidth, + pub modal_max_width: Option, } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] @@ -17,10 +15,10 @@ pub struct FileFinderSettingsContent { /// /// Default: true pub file_icons: Option, - /// The width of the file finder modal. + /// Determines how much space the file finder can take up in relation to the available window width. /// - /// Default: "medium" - pub modal_width: Option, + /// Default: small + pub modal_max_width: Option, } impl Settings for FileFinderSettings { @@ -36,40 +34,10 @@ impl Settings for FileFinderSettings { #[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum FileFinderWidth { - Small, #[default] + Small, Medium, Large, XLarge, Full, } - -impl FileFinderWidth { - const MIN_MODAL_WIDTH_PX: f32 = 384.; - - pub fn padding_px(&self) -> Pixels { - let padding_val = match self { - FileFinderWidth::Small => 1280., - FileFinderWidth::Medium => 1024., - FileFinderWidth::Large => 768., - FileFinderWidth::XLarge => 512., - FileFinderWidth::Full => 0., - }; - - Pixels(padding_val) - } - - pub fn calc_width(&self, window_width: Pixels) -> Pixels { - if self == &FileFinderWidth::Full { - return window_width; - } - - let min_modal_width_px = Pixels(FileFinderWidth::MIN_MODAL_WIDTH_PX); - - let padding_px = self.padding_px(); - let width_val = window_width - padding_px; - let finder_width = cmp::max(min_modal_width_px, width_val); - - finder_width - } -} diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index e6e6b662c0..ce8068fa3b 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -1418,11 +1418,11 @@ Or to set a `socks5` proxy: ## File Finder -### Modal Width +### Modal Max Width -- Description: Width of the file finder modal. Can take one of a few values: `small`, `medium`, `large`, `xlarge`, and `full`. -- Setting: `modal_width` -- Default: `medium` +- Description: Max-width of the file finder modal. It can take one of these values: `small`, `medium`, `large`, `xlarge`, and `full`. +- Setting: `max_modal_width` +- Default: `small` ## Preferred Line Length From e2552b9adda0e4db77bd6fdecc5e365b3c413d4e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 18 Nov 2024 16:37:28 -0500 Subject: [PATCH 033/157] collab: Bypass account age check for users with active LLM subscriptions (#20837) This PR makes it so users with an active LLM subscription can bypass the account age check. Release Notes: - N/A --- crates/collab/src/rpc.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 0e977074f7..397fcefacf 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -4030,12 +4030,15 @@ async fn get_llm_api_token( Err(anyhow!("terms of service not accepted"))? } - let mut account_created_at = user.created_at; - if let Some(github_created_at) = user.github_user_created_at { - account_created_at = account_created_at.min(github_created_at); - } - if Utc::now().naive_utc() - account_created_at < MIN_ACCOUNT_AGE_FOR_LLM_USE { - Err(anyhow!("account too young"))? + let has_llm_subscription = session.has_llm_subscription(&db).await?; + if !has_llm_subscription { + let mut account_created_at = user.created_at; + if let Some(github_created_at) = user.github_user_created_at { + account_created_at = account_created_at.min(github_created_at); + } + if Utc::now().naive_utc() - account_created_at < MIN_ACCOUNT_AGE_FOR_LLM_USE { + Err(anyhow!("account too young"))? + } } let billing_preferences = db.get_billing_preferences(user.id).await?; @@ -4045,7 +4048,7 @@ async fn get_llm_api_token( session.is_staff(), billing_preferences, has_llm_closed_beta_feature_flag, - session.has_llm_subscription(&db).await?, + has_llm_subscription, session.current_plan(&db).await?, &session.app_state.config, )?; From 5b317f60dff6090ff407e617a85bbbeaadfaad21 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Mon, 18 Nov 2024 21:39:57 +0000 Subject: [PATCH 034/157] Improve install-cmake script (#20836) - Don't output junk to stderr when cmake unavailable - Kitware PPA does not include up to date bins for all distros (e.g. Ubuntu 24 only has 3.30.2 although 3.30.4 has been out for a while) so don't try to force install a specific version. Take the best we can get. --- script/install-cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/install-cmake b/script/install-cmake index 71b5aaeeef..3a28aae1b8 100755 --- a/script/install-cmake +++ b/script/install-cmake @@ -35,7 +35,7 @@ CMAKE_VERSION="${CMAKE_VERSION:-${1:-3.30.4}}" if [ "$(whoami)" = root ]; then SUDO=; else SUDO="$(command -v sudo || command -v doas || true)"; fi -if cmake --version | grep -q "$CMAKE_VERSION"; then +if cmake --version 2>/dev/null | grep -q "$CMAKE_VERSION"; then echo "CMake $CMAKE_VERSION is already installed." exit 0 elif [ -e /usr/local/bin/cmake ]; then @@ -51,7 +51,7 @@ elif [ -e /etc/lsb-release ] && grep -qP 'DISTRIB_ID=Ubuntu' /etc/lsb-release; t echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" \ | $SUDO tee /etc/apt/sources.list.d/kitware.list >/dev/null $SUDO apt-get update - $SUDO apt-get install -y kitware-archive-keyring cmake==$CMAKE_VERSION + $SUDO apt-get install -y kitware-archive-keyring cmake else arch="$(uname -m)" if [ "$arch" != "x86_64" ] && [ "$arch" != "aarch64" ]; then From 5b9916e34bbe3824135bff3b6984e9329952de3a Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Mon, 18 Nov 2024 21:41:22 +0000 Subject: [PATCH 035/157] ci: Add shellcheck for scripts (#20631) Fixes shellcheck errors in script/* Adds a couple trailing newlines. Adds `script/shellcheck-scripts` and associated CI machinery. Current set ultra-conservative, does not output warnings, only errors. --- .github/workflows/script_checks.yml | 21 +++++++++++++++++++++ script/analyze_highlights.py | 1 + script/bundle-linux | 8 +++++--- script/clear-target-dir-if-larger-than | 2 +- script/deploy-postgrest | 2 +- script/get-crate-version | 4 ++-- script/kube-shell | 4 ++-- script/metal-debug | 2 +- script/shellcheck-scripts | 12 ++++++++++++ script/upload-nightly | 4 ++-- 10 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/script_checks.yml create mode 100755 script/shellcheck-scripts diff --git a/.github/workflows/script_checks.yml b/.github/workflows/script_checks.yml new file mode 100644 index 0000000000..c32a433e46 --- /dev/null +++ b/.github/workflows/script_checks.yml @@ -0,0 +1,21 @@ +name: Script + +on: + pull_request: + paths: + - "script/**" + push: + branches: + - main + +jobs: + shellcheck: + name: "ShellCheck Scripts" + if: github.repository_owner == 'zed-industries' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - name: Shellcheck ./scripts + run: | + ./script/shellcheck-scripts error diff --git a/script/analyze_highlights.py b/script/analyze_highlights.py index 1fd16f2c0f..09a6419653 100644 --- a/script/analyze_highlights.py +++ b/script/analyze_highlights.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 """ This script analyzes all the highlight.scm files in our embedded languages and extensions. It counts the number of unique instances of @{name} and the languages in which they are used. diff --git a/script/bundle-linux b/script/bundle-linux index 2aa1dcab4a..98b49ae4da 100755 --- a/script/bundle-linux +++ b/script/bundle-linux @@ -69,7 +69,9 @@ strip --strip-debug "${target_dir}/${remote_server_triple}/release/remote_server # Ensure that remote_server does not depend on libssl nor libcrypto, as we got rid of these deps. -! ldd "${target_dir}/${remote_server_triple}/release/remote_server" | grep -q 'libcrypto\|libssl' +if ldd "${target_dir}/${remote_server_triple}/release/remote_server" | grep -q 'libcrypto\|libssl'; then + echo "Error: remote_server still depends on libssl or libcrypto" && exit 1 +fi suffix="" if [ "$channel" != "stable" ]; then @@ -89,8 +91,8 @@ cp "${target_dir}/${target_triple}/release/cli" "${zed_dir}/bin/zed" # Libs find_libs() { ldd ${target_dir}/${target_triple}/release/zed |\ - cut -d' ' -f3 |\ - grep -v '\<\(libstdc++.so\|libc.so\|libgcc_s.so\|libm.so\|libpthread.so\|libdl.so\)' + cut -d' ' -f3 |\ + grep -v '\<\(libstdc++.so\|libc.so\|libgcc_s.so\|libm.so\|libpthread.so\|libdl.so\)' } mkdir -p "${zed_dir}/lib" diff --git a/script/clear-target-dir-if-larger-than b/script/clear-target-dir-if-larger-than index d23c111ec1..691ff42ffd 100755 --- a/script/clear-target-dir-if-larger-than +++ b/script/clear-target-dir-if-larger-than @@ -2,7 +2,7 @@ set -eu -if [[ $# < 1 ]]; then +if [[ $# -ne 1 ]]; then echo "usage: $0 " exit 1 fi diff --git a/script/deploy-postgrest b/script/deploy-postgrest index 14fbd50e30..2a0b21a991 100755 --- a/script/deploy-postgrest +++ b/script/deploy-postgrest @@ -3,7 +3,7 @@ set -eu source script/lib/deploy-helpers.sh -if [[ $# < 1 ]]; then +if [[ $# != 1 ]]; then echo "Usage: $0 (postgrest not needed on preview or nightly)" exit 1 fi diff --git a/script/get-crate-version b/script/get-crate-version index b6346b32ec..0a35e4d49d 100755 --- a/script/get-crate-version +++ b/script/get-crate-version @@ -2,7 +2,7 @@ set -eu -if [[ $# < 1 ]]; then +if [[ $# -ne 1 ]]; then echo "Usage: $0 " >&2 exit 1 fi @@ -14,4 +14,4 @@ cargo metadata \ --format-version=1 \ | jq \ --raw-output \ - ".packages[] | select(.name == \"${CRATE_NAME}\") | .version" \ No newline at end of file + ".packages[] | select(.name == \"${CRATE_NAME}\") | .version" diff --git a/script/kube-shell b/script/kube-shell index 9181dc959c..0ca77acdd0 100755 --- a/script/kube-shell +++ b/script/kube-shell @@ -1,6 +1,6 @@ #!/bin/bash -if [[ $# < 1 ]]; then +if [[ $# -ne 1 ]]; then echo "Usage: $0 [production|staging|...]" exit 1 fi @@ -8,4 +8,4 @@ fi export ZED_KUBE_NAMESPACE=$1 pod=$(kubectl --namespace=${ZED_KUBE_NAMESPACE} get pods --selector=app=zed --output=jsonpath='{.items[*].metadata.name}') -exec kubectl --namespace $ZED_KUBE_NAMESPACE exec --tty --stdin $pod -- /bin/bash \ No newline at end of file +exec kubectl --namespace $ZED_KUBE_NAMESPACE exec --tty --stdin $pod -- /bin/bash diff --git a/script/metal-debug b/script/metal-debug index 6fc18e5ebd..de8476f3e3 100755 --- a/script/metal-debug +++ b/script/metal-debug @@ -10,4 +10,4 @@ export GPUProfilerEnabled="YES" export METAL_DEBUG_ERROR_MODE=0 export LD_LIBRARY_PATH="/Applications/Xcode.app/Contents/Developer/../SharedFrameworks/" -cargo run $@ +cargo run "$@" diff --git a/script/shellcheck-scripts b/script/shellcheck-scripts new file mode 100755 index 0000000000..d42b31d02f --- /dev/null +++ b/script/shellcheck-scripts @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -euo pipefail + +mode=${1:-error} +[[ "$mode" =~ ^(error|warning)$ ]] || { echo "Usage: $0 [error|warning]"; exit 1; } + +cd "$(dirname "$0")/.." || exit 1 + +find script -maxdepth 1 -type f -print0 | + xargs -0 grep -l -E '^#!(/bin/|/usr/bin/env )(sh|bash|dash)' | + xargs -r shellcheck -x -S "$mode" -C diff --git a/script/upload-nightly b/script/upload-nightly index 61b73d4e56..fd37941981 100755 --- a/script/upload-nightly +++ b/script/upload-nightly @@ -19,12 +19,12 @@ if [[ -n "${1:-}" ]]; then target="$1" else echo "Error: Target '$1' is not allowed" - echo "Usage: $0 [${allowed_targets[@]}]" + echo "Usage: $0 [${allowed_targets[*]}]" exit 1 fi else echo "Error: Target is not specified" -echo "Usage: $0 [${allowed_targets[@]}]" +echo "Usage: $0 [${allowed_targets[*]}]" exit 1 fi echo "Uploading nightly for target: $target" From 889aac9c037dd39be8e912f288439b22ebb53603 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:56:34 -0500 Subject: [PATCH 036/157] Snippet choices (#13958) Closes: #12739 Release Notes: Solves #12739 by - Enable snippet parsing to successfully parse snippets with choices - Show completion menu when tabbing to a snippet variable with multiple choices Todo: - [x] Parse snippet choices - [x] Open completion menu when tabbing to a snippet variable with several choices (Thank you Piotr) - [x] Get snippet choices to reappear when tabbing back to a previous tabstop in a snippet - [x] add snippet unit tests - [x] Add fuzzy search to snippet choice completion menu & update completion menu based on choices - [x] add completion menu unit tests Current State: Using these custom snippets ```json "my snippet": { "prefix": "log", "body": ["type ${1|i32, u32|} = $2"], "description": "Expand `log` to `console.log()`" }, "my snippet2": { "prefix": "snip", "body": [ "type ${1|i,i8,i16,i64,i32|} ${2|test,test_again,test_final|} = $3" ], "description": "snippet choice tester" } ``` Using snippet choices: https://github.com/user-attachments/assets/d29fb1a2-7632-4071-944f-daeaa243e3ac --------- Co-authored-by: Piotr Osiewicz Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> --- crates/editor/src/debounced_delay.rs | 1 + crates/editor/src/editor.rs | 201 ++++++++++++++++++++++----- crates/editor/src/editor_tests.rs | 39 ++++++ crates/snippet/src/snippet.rs | 119 +++++++++++++++- 4 files changed, 321 insertions(+), 39 deletions(-) diff --git a/crates/editor/src/debounced_delay.rs b/crates/editor/src/debounced_delay.rs index 0dbf36d49e..ad4b55b209 100644 --- a/crates/editor/src/debounced_delay.rs +++ b/crates/editor/src/debounced_delay.rs @@ -5,6 +5,7 @@ use gpui::{Task, ViewContext}; use crate::Editor; +#[derive(Debug)] pub struct DebouncedDelay { task: Option>, cancel_channel: Option>, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9d8044f075..11d47daa6b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -883,6 +883,7 @@ struct AutocloseRegion { struct SnippetState { ranges: Vec>>, active_index: usize, + choices: Vec>>, } #[doc(hidden)] @@ -1000,7 +1001,7 @@ enum ContextMenuOrigin { GutterIndicator(DisplayRow), } -#[derive(Clone)] +#[derive(Clone, Debug)] struct CompletionsMenu { id: CompletionId, sort_completions: bool, @@ -1011,10 +1012,100 @@ struct CompletionsMenu { matches: Arc<[StringMatch]>, selected_item: usize, scroll_handle: UniformListScrollHandle, - selected_completion_documentation_resolve_debounce: Arc>, + selected_completion_documentation_resolve_debounce: Option>>, } impl CompletionsMenu { + fn new( + id: CompletionId, + sort_completions: bool, + initial_position: Anchor, + buffer: Model, + completions: Box<[Completion]>, + ) -> Self { + let match_candidates = completions + .iter() + .enumerate() + .map(|(id, completion)| StringMatchCandidate::new(id, completion.label.text.clone())) + .collect(); + + Self { + id, + sort_completions, + initial_position, + buffer, + completions: Arc::new(RwLock::new(completions)), + match_candidates, + matches: Vec::new().into(), + selected_item: 0, + scroll_handle: UniformListScrollHandle::new(), + selected_completion_documentation_resolve_debounce: Some(Arc::new(Mutex::new( + DebouncedDelay::new(), + ))), + } + } + + fn new_snippet_choices( + id: CompletionId, + sort_completions: bool, + choices: &Vec, + selection: Range, + buffer: Model, + ) -> Self { + let completions = choices + .iter() + .map(|choice| Completion { + old_range: selection.start.text_anchor..selection.end.text_anchor, + new_text: choice.to_string(), + label: CodeLabel { + text: choice.to_string(), + runs: Default::default(), + filter_range: Default::default(), + }, + server_id: LanguageServerId(usize::MAX), + documentation: None, + lsp_completion: Default::default(), + confirm: None, + }) + .collect(); + + let match_candidates = choices + .iter() + .enumerate() + .map(|(id, completion)| StringMatchCandidate::new(id, completion.to_string())) + .collect(); + let matches = choices + .iter() + .enumerate() + .map(|(id, completion)| StringMatch { + candidate_id: id, + score: 1., + positions: vec![], + string: completion.clone(), + }) + .collect(); + Self { + id, + sort_completions, + initial_position: selection.start, + buffer, + completions: Arc::new(RwLock::new(completions)), + match_candidates, + matches, + selected_item: 0, + scroll_handle: UniformListScrollHandle::new(), + selected_completion_documentation_resolve_debounce: Some(Arc::new(Mutex::new( + DebouncedDelay::new(), + ))), + } + } + + fn suppress_documentation_resolution(mut self) -> Self { + self.selected_completion_documentation_resolve_debounce + .take(); + self + } + fn select_first( &mut self, provider: Option<&dyn CompletionProvider>, @@ -1115,6 +1206,12 @@ impl CompletionsMenu { let Some(provider) = provider else { return; }; + let Some(documentation_resolve) = self + .selected_completion_documentation_resolve_debounce + .as_ref() + else { + return; + }; let resolve_task = provider.resolve_completions( self.buffer.clone(), @@ -1127,15 +1224,13 @@ impl CompletionsMenu { EditorSettings::get_global(cx).completion_documentation_secondary_query_debounce; let delay = Duration::from_millis(delay_ms); - self.selected_completion_documentation_resolve_debounce - .lock() - .fire_new(delay, cx, |_, cx| { - cx.spawn(move |this, mut cx| async move { - if let Some(true) = resolve_task.await.log_err() { - this.update(&mut cx, |_, cx| cx.notify()).ok(); - } - }) - }); + documentation_resolve.lock().fire_new(delay, cx, |_, cx| { + cx.spawn(move |this, mut cx| async move { + if let Some(true) = resolve_task.await.log_err() { + this.update(&mut cx, |_, cx| cx.notify()).ok(); + } + }) + }); } fn visible(&self) -> bool { @@ -1418,6 +1513,7 @@ impl CompletionsMenu { } } +#[derive(Clone)] struct AvailableCodeAction { excerpt_id: ExcerptId, action: CodeAction, @@ -4386,6 +4482,10 @@ impl Editor { return; }; + if !self.snippet_stack.is_empty() && self.context_menu.read().as_ref().is_some() { + return; + } + let position = self.selections.newest_anchor().head(); let (buffer, buffer_position) = if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) { @@ -4431,30 +4531,13 @@ impl Editor { })?; let completions = completions.await.log_err(); let menu = if let Some(completions) = completions { - let mut menu = CompletionsMenu { + let mut menu = CompletionsMenu::new( id, sort_completions, - initial_position: position, - match_candidates: completions - .iter() - .enumerate() - .map(|(id, completion)| { - StringMatchCandidate::new( - id, - completion.label.text[completion.label.filter_range.clone()] - .into(), - ) - }) - .collect(), - buffer: buffer.clone(), - completions: Arc::new(RwLock::new(completions.into())), - matches: Vec::new().into(), - selected_item: 0, - scroll_handle: UniformListScrollHandle::new(), - selected_completion_documentation_resolve_debounce: Arc::new(Mutex::new( - DebouncedDelay::new(), - )), - }; + position, + buffer.clone(), + completions.into(), + ); menu.filter(query.as_deref(), cx.background_executor().clone()) .await; @@ -4657,7 +4740,11 @@ impl Editor { self.transact(cx, |this, cx| { if let Some(mut snippet) = snippet { snippet.text = text.to_string(); - for tabstop in snippet.tabstops.iter_mut().flatten() { + for tabstop in snippet + .tabstops + .iter_mut() + .flat_map(|tabstop| tabstop.ranges.iter_mut()) + { tabstop.start -= common_prefix_len as isize; tabstop.end -= common_prefix_len as isize; } @@ -5693,6 +5780,27 @@ impl Editor { context_menu } + fn show_snippet_choices( + &mut self, + choices: &Vec, + selection: Range, + cx: &mut ViewContext, + ) { + if selection.start.buffer_id.is_none() { + return; + } + let buffer_id = selection.start.buffer_id.unwrap(); + let buffer = self.buffer().read(cx).buffer(buffer_id); + let id = post_inc(&mut self.next_completion_id); + + if let Some(buffer) = buffer { + *self.context_menu.write() = Some(ContextMenu::Completions( + CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer) + .suppress_documentation_resolution(), + )); + } + } + pub fn insert_snippet( &mut self, insertion_ranges: &[Range], @@ -5702,6 +5810,7 @@ impl Editor { struct Tabstop { is_end_tabstop: bool, ranges: Vec>, + choices: Option>, } let tabstops = self.buffer.update(cx, |buffer, cx| { @@ -5721,10 +5830,11 @@ impl Editor { .tabstops .iter() .map(|tabstop| { - let is_end_tabstop = tabstop.first().map_or(false, |tabstop| { + let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| { tabstop.is_empty() && tabstop.start == snippet.text.len() as isize }); let mut tabstop_ranges = tabstop + .ranges .iter() .flat_map(|tabstop_range| { let mut delta = 0_isize; @@ -5746,6 +5856,7 @@ impl Editor { Tabstop { is_end_tabstop, ranges: tabstop_ranges, + choices: tabstop.choices.clone(), } }) .collect::>() @@ -5755,16 +5866,29 @@ impl Editor { s.select_ranges(tabstop.ranges.iter().cloned()); }); + if let Some(choices) = &tabstop.choices { + if let Some(selection) = tabstop.ranges.first() { + self.show_snippet_choices(choices, selection.clone(), cx) + } + } + // If we're already at the last tabstop and it's at the end of the snippet, // we're done, we don't need to keep the state around. if !tabstop.is_end_tabstop { + let choices = tabstops + .iter() + .map(|tabstop| tabstop.choices.clone()) + .collect(); + let ranges = tabstops .into_iter() .map(|tabstop| tabstop.ranges) .collect::>(); + self.snippet_stack.push(SnippetState { active_index: 0, ranges, + choices, }); } @@ -5839,6 +5963,13 @@ impl Editor { self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_anchor_ranges(current_ranges.iter().cloned()) }); + + if let Some(choices) = &snippet.choices[snippet.active_index] { + if let Some(selection) = current_ranges.first() { + self.show_snippet_choices(&choices, selection.clone(), cx); + } + } + // If snippet state is not at the last tabstop, push it back on the stack if snippet.active_index + 1 < snippet.ranges.len() { self.snippet_stack.push(snippet); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 4469b2e614..2e4edf98bc 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -6551,6 +6551,45 @@ async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) { }); } +#[gpui::test] +async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let (text, insertion_ranges) = marked_text_ranges( + indoc! {" + ˇ + "}, + false, + ); + + let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx)); + let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx)); + + _ = editor.update(cx, |editor, cx| { + let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap(); + + editor + .insert_snippet(&insertion_ranges, snippet, cx) + .unwrap(); + + fn assert(editor: &mut Editor, cx: &mut ViewContext, marked_text: &str) { + let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false); + assert_eq!(editor.text(cx), expected_text); + assert_eq!(editor.selections.ranges::(cx), selection_ranges); + } + + assert( + editor, + cx, + indoc! {" + type «» =• + "}, + ); + + assert!(editor.context_menu_visible(), "There should be a matches"); + }); +} + #[gpui::test] async fn test_snippets(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); diff --git a/crates/snippet/src/snippet.rs b/crates/snippet/src/snippet.rs index 41529939a1..3eeaff285e 100644 --- a/crates/snippet/src/snippet.rs +++ b/crates/snippet/src/snippet.rs @@ -8,7 +8,11 @@ pub struct Snippet { pub tabstops: Vec, } -type TabStop = SmallVec<[Range; 2]>; +#[derive(Clone, Debug, Default, PartialEq)] +pub struct TabStop { + pub ranges: SmallVec<[Range; 2]>, + pub choices: Option>, +} impl Snippet { pub fn parse(source: &str) -> Result { @@ -24,7 +28,11 @@ impl Snippet { if let Some(final_tabstop) = final_tabstop { tabstops.push(final_tabstop); } else { - let end_tabstop = [len..len].into_iter().collect(); + let end_tabstop = TabStop { + ranges: [len..len].into_iter().collect(), + choices: None, + }; + if !tabstops.last().map_or(false, |t| *t == end_tabstop) { tabstops.push(end_tabstop); } @@ -88,11 +96,17 @@ fn parse_tabstop<'a>( ) -> Result<&'a str> { let tabstop_start = text.len(); let tabstop_index; + let mut choices = None; + if source.starts_with('{') { let (index, rest) = parse_int(&source[1..])?; tabstop_index = index; source = rest; + if source.starts_with("|") { + (source, choices) = parse_choices(&source[1..], text)?; + } + if source.starts_with(':') { source = parse_snippet(&source[1..], true, text, tabstops)?; } @@ -110,7 +124,11 @@ fn parse_tabstop<'a>( tabstops .entry(tabstop_index) - .or_default() + .or_insert_with(|| TabStop { + ranges: Default::default(), + choices, + }) + .ranges .push(tabstop_start as isize..text.len() as isize); Ok(source) } @@ -126,6 +144,61 @@ fn parse_int(source: &str) -> Result<(usize, &str)> { Ok((prefix.parse()?, suffix)) } +fn parse_choices<'a>( + mut source: &'a str, + text: &mut String, +) -> Result<(&'a str, Option>)> { + let mut found_default_choice = false; + let mut current_choice = String::new(); + let mut choices = Vec::new(); + + loop { + match source.chars().next() { + None => return Ok(("", Some(choices))), + Some('\\') => { + source = &source[1..]; + + if let Some(c) = source.chars().next() { + if !found_default_choice { + current_choice.push(c); + text.push(c); + } + source = &source[c.len_utf8()..]; + } + } + Some(',') => { + found_default_choice = true; + source = &source[1..]; + choices.push(current_choice); + current_choice = String::new(); + } + Some('|') => { + source = &source[1..]; + choices.push(current_choice); + return Ok((source, Some(choices))); + } + Some(_) => { + let chunk_end = source.find([',', '|', '\\']); + + if chunk_end.is_none() { + return Err(anyhow!( + "Placeholder choice doesn't contain closing pipe-character '|'" + )); + } + + let (chunk, rest) = source.split_at(chunk_end.unwrap()); + + if !found_default_choice { + text.push_str(chunk); + } + + current_choice.push_str(chunk); + source = rest; + } + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -142,11 +215,13 @@ mod tests { let snippet = Snippet::parse("one$1two").unwrap(); assert_eq!(snippet.text, "onetwo"); assert_eq!(tabstops(&snippet), &[vec![3..3], vec![6..6]]); + assert_eq!(tabstop_choices(&snippet), &[&None, &None]); // Multi-digit numbers let snippet = Snippet::parse("one$123-$99-two").unwrap(); assert_eq!(snippet.text, "one--two"); assert_eq!(tabstops(&snippet), &[vec![4..4], vec![3..3], vec![8..8]]); + assert_eq!(tabstop_choices(&snippet), &[&None, &None, &None]); } #[test] @@ -157,6 +232,7 @@ mod tests { // an additional tabstop at the end. assert_eq!(snippet.text, r#"foo."#); assert_eq!(tabstops(&snippet), &[vec![4..4]]); + assert_eq!(tabstop_choices(&snippet), &[&None]); } #[test] @@ -167,6 +243,7 @@ mod tests { // don't insert an additional tabstop at the end. assert_eq!(snippet.text, r#"
"#); assert_eq!(tabstops(&snippet), &[vec![12..12], vec![14..14]]); + assert_eq!(tabstop_choices(&snippet), &[&None, &None]); } #[test] @@ -177,6 +254,30 @@ mod tests { tabstops(&snippet), &[vec![3..6], vec![11..15], vec![15..15]] ); + assert_eq!(tabstop_choices(&snippet), &[&None, &None, &None]); + } + + #[test] + fn test_snippet_with_choice_placeholders() { + let snippet = Snippet::parse("type ${1|i32, u32|} = $2") + .expect("Should be able to unpack choice placeholders"); + + assert_eq!(snippet.text, "type i32 = "); + assert_eq!(tabstops(&snippet), &[vec![5..8], vec![11..11],]); + assert_eq!( + tabstop_choices(&snippet), + &[&Some(vec!["i32".to_string(), " u32".to_string()]), &None] + ); + + let snippet = Snippet::parse(r"${1|\$\{1\|one\,two\,tree\|\}|}") + .expect("Should be able to parse choice with escape characters"); + + assert_eq!(snippet.text, "${1|one,two,tree|}"); + assert_eq!(tabstops(&snippet), &[vec![0..18], vec![18..18]]); + assert_eq!( + tabstop_choices(&snippet), + &[&Some(vec!["${1|one,two,tree|}".to_string(),]), &None] + ); } #[test] @@ -196,6 +297,10 @@ mod tests { vec![40..40], ] ); + assert_eq!( + tabstop_choices(&snippet), + &[&None, &None, &None, &None, &None] + ); } #[test] @@ -203,10 +308,12 @@ mod tests { let snippet = Snippet::parse("\"\\$schema\": $1").unwrap(); assert_eq!(snippet.text, "\"$schema\": "); assert_eq!(tabstops(&snippet), &[vec![11..11]]); + assert_eq!(tabstop_choices(&snippet), &[&None]); let snippet = Snippet::parse("{a\\}").unwrap(); assert_eq!(snippet.text, "{a}"); assert_eq!(tabstops(&snippet), &[vec![3..3]]); + assert_eq!(tabstop_choices(&snippet), &[&None]); // backslash not functioning as an escape let snippet = Snippet::parse("a\\b").unwrap(); @@ -221,6 +328,10 @@ mod tests { } fn tabstops(snippet: &Snippet) -> Vec>> { - snippet.tabstops.iter().map(|t| t.to_vec()).collect() + snippet.tabstops.iter().map(|t| t.ranges.to_vec()).collect() + } + + fn tabstop_choices(snippet: &Snippet) -> Vec<&Option>> { + snippet.tabstops.iter().map(|t| &t.choices).collect() } } From 8666ec95bae9b4978906c18d8f07febab8ffcbe0 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Mon, 18 Nov 2024 22:17:24 +0000 Subject: [PATCH 037/157] ssh: Fix SSH to mac remotes (#20838) Restore ability to SSH to macOS arm remotes (`uname -m` on mac == `arm64`). Fix regression introduced in https://github.com/zed-industries/zed/pull/20618 --- crates/remote/src/ssh_session.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/remote/src/ssh_session.rs b/crates/remote/src/ssh_session.rs index 20795be201..1ea76a24c8 100644 --- a/crates/remote/src/ssh_session.rs +++ b/crates/remote/src/ssh_session.rs @@ -1568,6 +1568,7 @@ impl SshRemoteConnection { // exclude armv5,6,7 as they are 32-bit. let arch = if arch.starts_with("armv8") || arch.starts_with("armv9") + || arch.starts_with("arm64") || arch.starts_with("aarch64") { "aarch64" From b4c2f29c8bf6fa399a7788b01199ee518ebc8caf Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Mon, 18 Nov 2024 14:30:38 -0800 Subject: [PATCH 038/157] Remove use of current `File` for new buffers that never have `File` (#20832) `create_buffer` calls `Buffer::local` which sets `file` to `None` [here](https://github.com/zed-industries/zed/blob/f12981db32f9b936cd29e39ccc7f8a0b4e54cee1/crates/language/src/buffer.rs#L629). So there's no point in then immediately attempting to update maps that rely on `file` being present. Release Notes: - N/A --- crates/project/src/buffer_store.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index 634d87dca2..eb56680fb3 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -902,30 +902,12 @@ impl BufferStoreImpl for Model { } fn create_buffer(&self, cx: &mut ModelContext) -> Task>> { - let handle = self.clone(); cx.spawn(|buffer_store, mut cx| async move { let buffer = cx.new_model(|cx| { Buffer::local("", cx).with_language(language::PLAIN_TEXT.clone(), cx) })?; buffer_store.update(&mut cx, |buffer_store, cx| { buffer_store.add_buffer(buffer.clone(), cx).log_err(); - let buffer_id = buffer.read(cx).remote_id(); - handle.update(cx, |this, cx| { - if let Some(file) = File::from_dyn(buffer.read(cx).file()) { - this.local_buffer_ids_by_path.insert( - ProjectPath { - worktree_id: file.worktree_id(cx), - path: file.path.clone(), - }, - buffer_id, - ); - - if let Some(entry_id) = file.entry_id { - this.local_buffer_ids_by_entry_id - .insert(entry_id, buffer_id); - } - } - }); })?; Ok(buffer) }) From fb6c987e3e28ed006c32e66b59e791127308d7a4 Mon Sep 17 00:00:00 2001 From: Carroll Wainwright Date: Mon, 18 Nov 2024 15:05:39 -0800 Subject: [PATCH 039/157] python: Improve function syntax highlighting (#20487) Release Notes: - Differentiate between function and method calls and definitions. `function.definition` matches the highlight for e.g. rust, `function.call` is new. - Likewise differentiate between class calls and class definitions. - Better highlighting of function decorators (the `@` symbol is punctuation, and now the decorator itself has a `function.decorator` tag) - Make `cls` a special variable (like `self`) - Add `ellipsis` as a built-in constant Note that most themes do not currently make use of the `function.definition` tags, and none make use of the `type.class.definition` tag. Hopefully more themes will pick this up. *Before:* image *After:* image --- crates/languages/src/python/highlights.scm | 34 +++++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/crates/languages/src/python/highlights.scm b/crates/languages/src/python/highlights.scm index e5f1b4d423..78e5126d40 100644 --- a/crates/languages/src/python/highlights.scm +++ b/crates/languages/src/python/highlights.scm @@ -5,6 +5,14 @@ ; Type alias (type_alias_statement "type" @keyword) +; Identifier naming conventions + +((identifier) @type.class + (#match? @type.class "^[A-Z]")) + +((identifier) @constant + (#match? @constant "^_*[A-Z][A-Z\\d_]*$")) + ; TypeVar with constraints in type parameters (type (tuple (identifier) @type) @@ -12,25 +20,28 @@ ; Function calls -(decorator) @function +(decorator + "@" @punctuation.special + (identifier) @function.decorator) (call - function: (attribute attribute: (identifier) @function.method)) + function: (attribute attribute: (identifier) @function.method.call)) (call - function: (identifier) @function) + function: (identifier) @function.call) -; Function definitions +; Function and class definitions (function_definition - name: (identifier) @function) + name: (identifier) @function.definition) -; Identifier naming conventions +; Class definitions and calling: needs to come after the regex matching above -((identifier) @type - (#match? @type "^[A-Z]")) +(class_definition + name: (identifier) @type.class.definition) -((identifier) @constant - (#match? @constant "^_*[A-Z][A-Z\\d_]*$")) +(call + function: (identifier) @type.class.call + (#match? @type.class.call "^[A-Z][A-Z0-9_]*[a-z]")) ; Builtin functions @@ -46,6 +57,7 @@ (none) (true) (false) + (ellipsis) ] @constant.builtin [ @@ -58,7 +70,7 @@ [ (parameters (identifier) @variable.special) (attribute (identifier) @variable.special) - (#match? @variable.special "^self$") + (#match? @variable.special "^self|cls$") ] (comment) @comment From 80d50f56f3456ab184f680ed7afb9e134493eda2 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Mon, 18 Nov 2024 18:20:32 -0500 Subject: [PATCH 040/157] collab: Add feature flag to bypass account age check (#20843) This PR adds a `bypass-account-age-check` feature flag that can be used to bypass the minimum account age check. Release Notes: - N/A --- crates/collab/src/rpc.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 397fcefacf..1184c48618 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -4031,7 +4031,10 @@ async fn get_llm_api_token( } let has_llm_subscription = session.has_llm_subscription(&db).await?; - if !has_llm_subscription { + + let bypass_account_age_check = + has_llm_subscription || flags.iter().any(|flag| flag == "bypass-account-age-check"); + if !bypass_account_age_check { let mut account_created_at = user.created_at; if let Some(github_created_at) = user.github_user_created_at { account_created_at = account_created_at.min(github_created_at); From f0c7e62adc5f69866eb6434b78d552780adf47d2 Mon Sep 17 00:00:00 2001 From: lord Date: Mon, 18 Nov 2024 18:32:43 -0500 Subject: [PATCH 041/157] Leave goal_x unchanged when moving by rows past the start or end of the document (#20705) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Perhaps this was intentional behavior, but if not, I've attempted to write this hacky fix — I noticed using the vertical arrow keys to move past the document start/end would reset the goal_x to either zero (for moving upwards) or the line width (for moving downwards). This change makes Zed match most native text fields (at least on macOS) which leave goal_x unchanged, even when hitting the end of the document. I tested this change manually. Would be happy to add automatic tests for it too, but couldn't find any existing cursor movement tests. Release Notes: - Behavior when moving vertically past the start or end of a document now matches native text fields; it no longer resets the selection goal --- crates/editor/src/editor_tests.rs | 28 ++++++++++++++++++++++++++++ crates/editor/src/movement.rs | 18 ++++++++---------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 2e4edf98bc..01507c4e31 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -1398,6 +1398,15 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { view.change_selections(None, cx, |s| { s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]); }); + + // moving above start of document should move selection to start of document, + // but the next move down should still be at the original goal_x + view.move_up(&MoveUp, cx); + assert_eq!( + view.selections.display_ranges(cx), + &[empty_range(0, "".len())] + ); + view.move_down(&MoveDown, cx); assert_eq!( view.selections.display_ranges(cx), @@ -1422,6 +1431,25 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) { &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())] ); + // moving past end of document should not change goal_x + view.move_down(&MoveDown, cx); + assert_eq!( + view.selections.display_ranges(cx), + &[empty_range(5, "".len())] + ); + + view.move_down(&MoveDown, cx); + assert_eq!( + view.selections.display_ranges(cx), + &[empty_range(5, "".len())] + ); + + view.move_up(&MoveUp, cx); + assert_eq!( + view.selections.display_ranges(cx), + &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())] + ); + view.move_up(&MoveUp, cx); assert_eq!( view.selections.display_ranges(cx), diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 19ba147e16..52bedde2e3 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -3,7 +3,7 @@ use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint}; use crate::{scroll::ScrollAnchor, CharKind, DisplayRow, EditorStyle, RowExt, ToOffset, ToPoint}; -use gpui::{px, Pixels, WindowTextSystem}; +use gpui::{Pixels, WindowTextSystem}; use language::Point; use multi_buffer::{MultiBufferRow, MultiBufferSnapshot}; use serde::Deserialize; @@ -120,7 +120,7 @@ pub(crate) fn up_by_rows( preserve_column_at_start: bool, text_layout_details: &TextLayoutDetails, ) -> (DisplayPoint, SelectionGoal) { - let mut goal_x = match goal { + let goal_x = match goal { SelectionGoal::HorizontalPosition(x) => x.into(), SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(), SelectionGoal::HorizontalRange { end, .. } => end.into(), @@ -138,7 +138,6 @@ pub(crate) fn up_by_rows( return (start, goal); } else { point = DisplayPoint::new(DisplayRow(0), 0); - goal_x = px(0.); } let mut clipped_point = map.clip_point(point, Bias::Left); @@ -159,7 +158,7 @@ pub(crate) fn down_by_rows( preserve_column_at_end: bool, text_layout_details: &TextLayoutDetails, ) -> (DisplayPoint, SelectionGoal) { - let mut goal_x = match goal { + let goal_x = match goal { SelectionGoal::HorizontalPosition(x) => x.into(), SelectionGoal::WrappedHorizontalPosition((_, x)) => x.into(), SelectionGoal::HorizontalRange { end, .. } => end.into(), @@ -174,7 +173,6 @@ pub(crate) fn down_by_rows( return (start, goal); } else { point = map.max_point(); - goal_x = map.x_for_display_point(point, text_layout_details) } let mut clipped_point = map.clip_point(point, Bias::Right); @@ -610,7 +608,7 @@ mod tests { test::{editor_test_context::EditorTestContext, marked_display_snapshot}, Buffer, DisplayMap, DisplayRow, ExcerptRange, FoldPlaceholder, InlayId, MultiBuffer, }; - use gpui::{font, Context as _}; + use gpui::{font, px, Context as _}; use language::Capability; use project::Project; use settings::SettingsStore; @@ -977,7 +975,7 @@ mod tests { ), ( DisplayPoint::new(DisplayRow(2), 0), - SelectionGoal::HorizontalPosition(0.0) + SelectionGoal::HorizontalPosition(col_2_x.0), ), ); assert_eq!( @@ -990,7 +988,7 @@ mod tests { ), ( DisplayPoint::new(DisplayRow(2), 0), - SelectionGoal::HorizontalPosition(0.0) + SelectionGoal::HorizontalPosition(0.0), ), ); @@ -1059,7 +1057,7 @@ mod tests { let max_point_x = snapshot .x_for_display_point(DisplayPoint::new(DisplayRow(7), 2), &text_layout_details); - // Can't move down off the end + // Can't move down off the end, and attempting to do so leaves the selection goal unchanged assert_eq!( down( &snapshot, @@ -1070,7 +1068,7 @@ mod tests { ), ( DisplayPoint::new(DisplayRow(7), 2), - SelectionGoal::HorizontalPosition(max_point_x.0) + SelectionGoal::HorizontalPosition(0.0) ), ); assert_eq!( From d4c5c0f05e69eab17e412bb3480546a145a8f7d7 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 18 Nov 2024 16:47:25 -0700 Subject: [PATCH 042/157] Don't render invisibles with elements (#20841) Turns out that in the case you have a somehow valid utf-8 file that contains almost all ascii control characters, we run out of element arena space. Fixes: #20652 Release Notes: - Fixed a crash when opening a file containing a very large number of ascii control characters on one line. --- crates/editor/src/display_map.rs | 51 +++++---- crates/editor/src/element.rs | 116 +++++++++++++-------- crates/gpui/src/text_system/line.rs | 15 +++ crates/gpui/src/text_system/line_layout.rs | 2 +- 4 files changed, 119 insertions(+), 65 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index f2e986b91b..b95c9312c5 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -66,7 +66,7 @@ use std::{ use sum_tree::{Bias, TreeMap}; use tab_map::{TabMap, TabSnapshot}; use text::LineIndent; -use ui::{div, px, IntoElement, ParentElement, SharedString, Styled, WindowContext}; +use ui::{px, SharedString, WindowContext}; use unicode_segmentation::UnicodeSegmentation; use wrap_map::{WrapMap, WrapSnapshot}; @@ -541,11 +541,17 @@ pub struct HighlightStyles { pub suggestion: Option, } +#[derive(Clone)] +pub enum ChunkReplacement { + Renderer(ChunkRenderer), + Str(SharedString), +} + pub struct HighlightedChunk<'a> { pub text: &'a str, pub style: Option, pub is_tab: bool, - pub renderer: Option, + pub replacement: Option, } impl<'a> HighlightedChunk<'a> { @@ -557,7 +563,7 @@ impl<'a> HighlightedChunk<'a> { let mut text = self.text; let style = self.style; let is_tab = self.is_tab; - let renderer = self.renderer; + let renderer = self.replacement; iter::from_fn(move || { let mut prefix_len = 0; while let Some(&ch) = chars.peek() { @@ -573,30 +579,33 @@ impl<'a> HighlightedChunk<'a> { text: prefix, style, is_tab, - renderer: renderer.clone(), + replacement: renderer.clone(), }); } chars.next(); let (prefix, suffix) = text.split_at(ch.len_utf8()); text = suffix; if let Some(replacement) = replacement(ch) { - let background = editor_style.status.hint_background; - let underline = editor_style.status.hint; + let invisible_highlight = HighlightStyle { + background_color: Some(editor_style.status.hint_background), + underline: Some(UnderlineStyle { + color: Some(editor_style.status.hint), + thickness: px(1.), + wavy: false, + }), + ..Default::default() + }; + let invisible_style = if let Some(mut style) = style { + style.highlight(invisible_highlight); + style + } else { + invisible_highlight + }; return Some(HighlightedChunk { text: prefix, - style: None, + style: Some(invisible_style), is_tab: false, - renderer: Some(ChunkRenderer { - render: Arc::new(move |_| { - div() - .child(replacement) - .bg(background) - .text_decoration_1() - .text_decoration_color(underline) - .into_any_element() - }), - constrain_width: false, - }), + replacement: Some(ChunkReplacement::Str(replacement.into())), }); } else { let invisible_highlight = HighlightStyle { @@ -619,7 +628,7 @@ impl<'a> HighlightedChunk<'a> { text: prefix, style: Some(invisible_style), is_tab: false, - renderer: renderer.clone(), + replacement: renderer.clone(), }); } } @@ -631,7 +640,7 @@ impl<'a> HighlightedChunk<'a> { text: remainder, style, is_tab, - renderer: renderer.clone(), + replacement: renderer.clone(), }) } else { None @@ -895,7 +904,7 @@ impl DisplaySnapshot { text: chunk.text, style: highlight_style, is_tab: chunk.is_tab, - renderer: chunk.renderer, + replacement: chunk.renderer.map(ChunkReplacement::Renderer), } .highlight_invisibles(editor_style) }) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 56322ff9f5..7702134409 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -16,8 +16,8 @@ use crate::{ items::BufferSearchHighlights, mouse_context_menu::{self, MenuPosition, MouseContextMenu}, scroll::scroll_amount::ScrollAmount, - BlockId, CodeActionsMenu, CursorShape, CustomBlockId, DisplayPoint, DisplayRow, - DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings, + BlockId, ChunkReplacement, CodeActionsMenu, CursorShape, CustomBlockId, DisplayPoint, + DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap, ToPoint, @@ -34,8 +34,8 @@ use gpui::{ FontId, GlobalElementId, Hitbox, Hsla, InteractiveElement, IntoElement, Length, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, ParentElement, Pixels, ScrollDelta, ScrollWheelEvent, ShapedLine, SharedString, Size, - StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, TextStyleRefinement, View, - ViewContext, WeakView, WindowContext, + StatefulInteractiveElement, Style, Styled, TextRun, TextStyleRefinement, View, ViewContext, + WeakView, WindowContext, }; use gpui::{ClickEvent, Subscription}; use itertools::Itertools; @@ -2019,7 +2019,7 @@ impl EditorElement { let chunks = snapshot.highlighted_chunks(rows.clone(), true, style); LineWithInvisibles::from_chunks( chunks, - &style.text, + &style, MAX_LINE_LEN, rows.len(), snapshot.mode, @@ -4372,7 +4372,7 @@ impl LineWithInvisibles { #[allow(clippy::too_many_arguments)] fn from_chunks<'a>( chunks: impl Iterator>, - text_style: &TextStyle, + editor_style: &EditorStyle, max_line_len: usize, max_line_count: usize, editor_mode: EditorMode, @@ -4380,6 +4380,7 @@ impl LineWithInvisibles { is_row_soft_wrapped: impl Copy + Fn(usize) -> bool, cx: &mut WindowContext, ) -> Vec { + let text_style = &editor_style.text; let mut layouts = Vec::with_capacity(max_line_count); let mut fragments: SmallVec<[LineFragment; 1]> = SmallVec::new(); let mut line = String::new(); @@ -4398,9 +4399,9 @@ impl LineWithInvisibles { text: "\n", style: None, is_tab: false, - renderer: None, + replacement: None, }]) { - if let Some(renderer) = highlighted_chunk.renderer { + if let Some(replacement) = highlighted_chunk.replacement { if !line.is_empty() { let shaped_line = cx .text_system() @@ -4413,42 +4414,71 @@ impl LineWithInvisibles { styles.clear(); } - let available_width = if renderer.constrain_width { - let chunk = if highlighted_chunk.text == ellipsis.as_ref() { - ellipsis.clone() - } else { - SharedString::from(Arc::from(highlighted_chunk.text)) - }; - let shaped_line = cx - .text_system() - .shape_line( - chunk, - font_size, - &[text_style.to_run(highlighted_chunk.text.len())], - ) - .unwrap(); - AvailableSpace::Definite(shaped_line.width) - } else { - AvailableSpace::MinContent - }; + match replacement { + ChunkReplacement::Renderer(renderer) => { + let available_width = if renderer.constrain_width { + let chunk = if highlighted_chunk.text == ellipsis.as_ref() { + ellipsis.clone() + } else { + SharedString::from(Arc::from(highlighted_chunk.text)) + }; + let shaped_line = cx + .text_system() + .shape_line( + chunk, + font_size, + &[text_style.to_run(highlighted_chunk.text.len())], + ) + .unwrap(); + AvailableSpace::Definite(shaped_line.width) + } else { + AvailableSpace::MinContent + }; - let mut element = (renderer.render)(&mut ChunkRendererContext { - context: cx, - max_width: text_width, - }); - let line_height = text_style.line_height_in_pixels(cx.rem_size()); - let size = element.layout_as_root( - size(available_width, AvailableSpace::Definite(line_height)), - cx, - ); + let mut element = (renderer.render)(&mut ChunkRendererContext { + context: cx, + max_width: text_width, + }); + let line_height = text_style.line_height_in_pixels(cx.rem_size()); + let size = element.layout_as_root( + size(available_width, AvailableSpace::Definite(line_height)), + cx, + ); - width += size.width; - len += highlighted_chunk.text.len(); - fragments.push(LineFragment::Element { - element: Some(element), - size, - len: highlighted_chunk.text.len(), - }); + width += size.width; + len += highlighted_chunk.text.len(); + fragments.push(LineFragment::Element { + element: Some(element), + size, + len: highlighted_chunk.text.len(), + }); + } + ChunkReplacement::Str(x) => { + let text_style = if let Some(style) = highlighted_chunk.style { + Cow::Owned(text_style.clone().highlight(style)) + } else { + Cow::Borrowed(text_style) + }; + + let run = TextRun { + len: x.len(), + font: text_style.font(), + color: text_style.color, + background_color: text_style.background_color, + underline: text_style.underline, + strikethrough: text_style.strikethrough, + }; + let line_layout = cx + .text_system() + .shape_line(x, font_size, &[run]) + .unwrap() + .with_len(highlighted_chunk.text.len()); + + width += line_layout.width; + len += highlighted_chunk.text.len(); + fragments.push(LineFragment::Text(line_layout)) + } + } } else { for (ix, mut line_chunk) in highlighted_chunk.text.split('\n').enumerate() { if ix > 0 { @@ -5992,7 +6022,7 @@ fn layout_line( let chunks = snapshot.highlighted_chunks(row..row + DisplayRow(1), true, style); LineWithInvisibles::from_chunks( chunks, - &style.text, + &style, MAX_LINE_LEN, 1, snapshot.mode, diff --git a/crates/gpui/src/text_system/line.rs b/crates/gpui/src/text_system/line.rs index b8b698a042..7c18684cbc 100644 --- a/crates/gpui/src/text_system/line.rs +++ b/crates/gpui/src/text_system/line.rs @@ -44,6 +44,21 @@ impl ShapedLine { self.layout.len } + /// Override the len, useful if you're rendering text a + /// as text b (e.g. rendering invisibles). + pub fn with_len(mut self, len: usize) -> Self { + let layout = self.layout.as_ref(); + self.layout = Arc::new(LineLayout { + font_size: layout.font_size, + width: layout.width, + ascent: layout.ascent, + descent: layout.descent, + runs: layout.runs.clone(), + len, + }); + self + } + /// Paint the line of text to the window. pub fn paint( &self, diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index 7e5a43dee8..66eb914a30 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/crates/gpui/src/text_system/line_layout.rs @@ -29,7 +29,7 @@ pub struct LineLayout { } /// A run of text that has been shaped . -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ShapedRun { /// The font id for this run pub font_id: FontId, From e7a0890086fbb99a2543bdfcc1d815d8d9ce6e7d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 18 Nov 2024 16:47:36 -0700 Subject: [PATCH 043/157] Don't call setAllowsAutomaticKeyEquivalentLocalization on Big Sur (#20844) Closes #20821 Release Notes: - Fixed a crash on Big Sur (preview only) --- crates/gpui/src/platform/mac/platform.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index b744c658ce..faf9329734 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -343,8 +343,10 @@ impl MacPlatform { ns_string(key_to_native(&keystroke.key).as_ref()), ) .autorelease(); - let _: () = - msg_send![item, setAllowsAutomaticKeyEquivalentLocalization: NO]; + if MacPlatform::os_version().unwrap() >= SemanticVersion::new(12, 0, 0) { + let _: () = + msg_send![item, setAllowsAutomaticKeyEquivalentLocalization: NO]; + } item.setKeyEquivalentModifierMask_(mask); } // For multi-keystroke bindings, render the keystroke as part of the title. From 343c88574a6b9b701fc7dcf0edba185cfc1eb28f Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Tue, 19 Nov 2024 00:56:45 +0000 Subject: [PATCH 044/157] Improve file_types in default.json (#20429) Detect .env.* as Shell Script Move non glob json/jsonc/toml file_types into langauges/*/config.toml --- assets/settings/default.json | 11 ++--------- crates/languages/src/json/config.toml | 2 +- crates/languages/src/jsonc/config.toml | 2 +- extensions/toml/languages/toml/config.toml | 2 +- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 2352e75ee9..7c4a9a8111 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -873,15 +873,8 @@ // "file_types": { "Plain Text": ["txt"], - "JSON": ["flake.lock"], - "JSONC": [ - "**/.zed/**/*.json", - "**/zed/**/*.json", - "**/Zed/**/*.json", - "tsconfig.json", - "pyrightconfig.json" - ], - "TOML": ["uv.lock"] + "JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json"], + "Shell Script": [".env.*"] }, /// By default use a recent system version of node, or install our own. /// You can override this to use a version of node that is not in $PATH with: diff --git a/crates/languages/src/json/config.toml b/crates/languages/src/json/config.toml index c4a91c20b0..dc49f4f36e 100644 --- a/crates/languages/src/json/config.toml +++ b/crates/languages/src/json/config.toml @@ -1,6 +1,6 @@ name = "JSON" grammar = "json" -path_suffixes = ["json"] +path_suffixes = ["json", "flake.lock"] line_comments = ["// "] autoclose_before = ",]}" brackets = [ diff --git a/crates/languages/src/jsonc/config.toml b/crates/languages/src/jsonc/config.toml index fe62764b27..226ae92912 100644 --- a/crates/languages/src/jsonc/config.toml +++ b/crates/languages/src/jsonc/config.toml @@ -1,6 +1,6 @@ name = "JSONC" grammar = "jsonc" -path_suffixes = ["jsonc"] +path_suffixes = ["jsonc", "tsconfig.json", "pyrightconfig.json"] line_comments = ["// "] autoclose_before = ",]}" brackets = [ diff --git a/extensions/toml/languages/toml/config.toml b/extensions/toml/languages/toml/config.toml index d5c1172d84..f62290d9e9 100644 --- a/extensions/toml/languages/toml/config.toml +++ b/extensions/toml/languages/toml/config.toml @@ -1,6 +1,6 @@ name = "TOML" grammar = "toml" -path_suffixes = ["Cargo.lock", "toml", "Pipfile"] +path_suffixes = ["Cargo.lock", "toml", "Pipfile", "uv.lock"] line_comments = ["# "] autoclose_before = ",]}" brackets = [ From bd0f1974157bcc39c488e3ce86bc4f9b6d1a710d Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Mon, 18 Nov 2024 18:12:23 -0800 Subject: [PATCH 045/157] Create `RunningKernel` trait to allow for native and remote jupyter kernels (#20842) Starts setting up a `RunningKernel` trait to make the remote kernel implementation easy to get started with. No release notes until this is all hooked up. Release Notes: - N/A --- Cargo.lock | 1985 ++++++++++------- Cargo.toml | 6 +- crates/quick_action_bar/src/repl_menu.rs | 2 +- crates/repl/Cargo.toml | 2 + crates/repl/src/kernels/mod.rs | 227 ++ .../{kernels.rs => kernels/native_kernel.rs} | 304 +-- crates/repl/src/kernels/remote_kernels.rs | 122 + crates/repl/src/repl.rs | 2 +- crates/repl/src/session.rs | 38 +- 9 files changed, 1600 insertions(+), 1088 deletions(-) create mode 100644 crates/repl/src/kernels/mod.rs rename crates/repl/src/{kernels.rs => kernels/native_kernel.rs} (62%) create mode 100644 crates/repl/src/kernels/remote_kernels.rs diff --git a/Cargo.lock b/Cargo.lock index aeded0f367..7f5934fca8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ dependencies = [ "auto_update", "editor", "extension_host", - "futures 0.3.30", + "futures 0.3.31", "gpui", "language", "lsp", @@ -23,19 +23,13 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "gimli 0.31.0", + "gimli 0.31.1", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -100,8 +94,8 @@ dependencies = [ "miow", "parking_lot", "piper", - "polling 3.7.3", - "regex-automata 0.4.7", + "polling 3.7.4", + "regex-automata 0.4.9", "rustix-openpty", "serde", "signal-hook", @@ -124,9 +118,9 @@ checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "alsa" @@ -192,9 +186,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -207,36 +201,36 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -245,13 +239,13 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", - "futures 0.3.30", + "futures 0.3.31", "http_client", "schemars", "serde", "serde_json", "strum 0.25.0", - "thiserror", + "thiserror 1.0.69", "util", ] @@ -278,9 +272,9 @@ dependencies = [ [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "arg_enum_proc_macro" @@ -301,9 +295,9 @@ checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -396,7 +390,7 @@ dependencies = [ "env_logger 0.11.5", "feature_flags", "fs", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "globset", "gpui", @@ -463,7 +457,7 @@ dependencies = [ "collections", "derive_more", "extension", - "futures 0.3.30", + "futures 0.3.31", "gpui", "language", "language_model", @@ -573,14 +567,14 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.1.1", - "futures-lite 2.3.0", + "fastrand 2.2.0", + "futures-lite 2.5.0", "slab", ] @@ -604,7 +598,7 @@ checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ "async-lock 3.4.0", "blocking", - "futures-lite 2.3.0", + "futures-lite 2.5.0", ] [[package]] @@ -615,10 +609,10 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.3.1", "async-executor", - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", "blocking", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "once_cell", ] @@ -644,18 +638,18 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock 3.4.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "parking", - "polling 3.7.3", - "rustix 0.38.35", + "polling 3.7.4", + "rustix 0.38.40", "slab", "tracing", "windows-sys 0.59.0", @@ -689,7 +683,7 @@ checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" dependencies = [ "futures-util", "native-tls", - "thiserror", + "thiserror 1.0.69", "url", ] @@ -710,9 +704,9 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ - "async-io 2.3.4", + "async-io 2.4.0", "blocking", - "futures-lite 2.3.0", + "futures-lite 2.5.0", ] [[package]] @@ -720,7 +714,7 @@ name = "async-pipe" version = "0.1.3" source = "git+https://github.com/zed-industries/async-pipe-rs?rev=82d00a04211cf4e1236029aa03e6b6ce2a74c553#82d00a04211cf4e1236029aa03e6b6ce2a74c553" dependencies = [ - "futures 0.3.30", + "futures 0.3.31", "log", ] @@ -737,28 +731,27 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.48.0", ] [[package]] name = "async-process" -version = "2.2.4" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ "async-channel 2.3.1", - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", "async-signal", "async-task", "blocking", "cfg-if", "event-listener 5.3.1", - "futures-lite 2.3.0", - "rustix 0.38.35", + "futures-lite 2.5.0", + "rustix 0.38.40", "tracing", - "windows-sys 0.59.0", ] [[package]] @@ -789,13 +782,13 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.35", + "rustix 0.38.40", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -803,21 +796,21 @@ dependencies = [ [[package]] name = "async-std" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", - "async-process 1.8.1", + "async-io 2.4.0", + "async-lock 3.4.0", + "async-process 2.3.0", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 1.13.0", + "futures-lite 2.5.0", "gloo-timers", "kv-log-macro", "log", @@ -831,9 +824,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -842,9 +835,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", @@ -867,7 +860,7 @@ dependencies = [ "serde_qs 0.10.1", "smart-default", "smol_str", - "thiserror", + "thiserror 1.0.69", "tokio", ] @@ -915,6 +908,20 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "async-tungstenite" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce01ac37fdc85f10a43c43bc582cbd566720357011578a935761075f898baf58" +dependencies = [ + "async-std", + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tungstenite 0.19.0", +] + [[package]] name = "async-tungstenite" version = "0.28.0" @@ -947,9 +954,9 @@ checksum = "00b9f7252833d5ed4b00aa9604b563529dd5e11de9c23615de2dcdf91eb87b52" dependencies = [ "async-compression", "crc32fast", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "pin-project", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -958,7 +965,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-sink", "futures-util", "memchr", @@ -1028,9 +1035,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "av1-grain" @@ -1048,18 +1055,18 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2" +checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" dependencies = [ "arrayvec", ] [[package]] name = "aws-config" -version = "1.5.5" +version = "1.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e95816a168520d72c0e7680c405a5a8c1fb6a035b4bc4b9d7b0de8e1a941697" +checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1073,8 +1080,8 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.2", - "fastrand 2.1.1", + "bytes 1.8.0", + "fastrand 2.2.0", "hex", "http 0.2.12", "ring", @@ -1112,8 +1119,8 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.2", - "fastrand 2.1.1", + "bytes 1.8.0", + "fastrand 2.2.0", "http 0.2.12", "http-body 0.4.6", "once_cell", @@ -1138,7 +1145,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.2", + "bytes 1.8.0", "http 0.2.12", "once_cell", "regex-lite", @@ -1147,11 +1154,10 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.47.0" +version = "1.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca49303c05d2a740b8a4552fac63a4db6ead84f7e7eeed04761fd3014c26f25" +checksum = "0e531658a0397d22365dfe26c3e1c0c8448bf6a3a2d8a098ded802f2b1261615" dependencies = [ - "ahash 0.8.11", "aws-credential-types", "aws-runtime", "aws-sigv4", @@ -1165,8 +1171,8 @@ dependencies = [ "aws-smithy-types", "aws-smithy-xml", "aws-types", - "bytes 1.7.2", - "fastrand 2.1.1", + "bytes 1.8.0", + "fastrand 2.2.0", "hex", "hmac", "http 0.2.12", @@ -1182,9 +1188,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.40.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5879bec6e74b648ce12f6085e7245417bc5f6d672781028384d2e494be3eb6d" +checksum = "09677244a9da92172c8dc60109b4a9658597d4d298b188dd0018b6a66b410ca4" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1195,7 +1201,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.2", + "bytes 1.8.0", "http 0.2.12", "once_cell", "regex-lite", @@ -1204,9 +1210,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.41.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef4cd9362f638c22a3b959fd8df292e7e47fdf170270f86246b97109b5f2f7d" +checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1217,7 +1223,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", - "bytes 1.7.2", + "bytes 1.8.0", "http 0.2.12", "once_cell", "regex-lite", @@ -1226,9 +1232,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.40.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1e2735d2ab28b35ecbb5496c9d41857f52a0d6a0075bbf6a8af306045ea6f6" +checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1258,7 +1264,7 @@ dependencies = [ "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.7.2", + "bytes 1.8.0", "crypto-bigint 0.5.5", "form_urlencoded", "hex", @@ -1289,13 +1295,13 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.60.12" +version = "0.60.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598b1689d001c4d4dc3cb386adb07d37786783aee3ac4b324bcadac116bf3d23" +checksum = "ba1a71073fca26775c8b5189175ea8863afb1c9ea2cceb02a5de5ad9dfbaa795" dependencies = [ "aws-smithy-http", "aws-smithy-types", - "bytes 1.7.2", + "bytes 1.8.0", "crc32c", "crc32fast", "hex", @@ -1315,7 +1321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cef7d0a272725f87e51ba2bf89f8c21e4df61b9e49ae1ac367a6d69916ef7c90" dependencies = [ "aws-smithy-types", - "bytes 1.7.2", + "bytes 1.8.0", "crc32fast", ] @@ -1328,7 +1334,7 @@ dependencies = [ "aws-smithy-eventstream", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.7.2", + "bytes 1.8.0", "bytes-utils", "futures-core", "http 0.2.12", @@ -1369,8 +1375,8 @@ dependencies = [ "aws-smithy-http", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes 1.7.2", - "fastrand 2.1.1", + "bytes 1.8.0", + "fastrand 2.2.0", "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", @@ -1394,7 +1400,7 @@ checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" dependencies = [ "aws-smithy-async", "aws-smithy-types", - "bytes 1.7.2", + "bytes 1.8.0", "http 0.2.12", "http 1.1.0", "pin-project-lite", @@ -1410,7 +1416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" dependencies = [ "base64-simd", - "bytes 1.7.2", + "bytes 1.8.0", "bytes-utils", "futures-core", "http 0.2.12", @@ -1431,9 +1437,9 @@ dependencies = [ [[package]] name = "aws-smithy-xml" -version = "0.60.8" +version = "0.60.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d123fbc2a4adc3c301652ba8e149bf4bc1d1725affb9784eb20c953ace06bf55" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" dependencies = [ "xmlparser", ] @@ -1462,7 +1468,7 @@ dependencies = [ "axum-core", "base64 0.21.7", "bitflags 1.3.2", - "bytes 1.7.2", + "bytes 1.8.0", "futures-util", "headers", "http 0.2.12", @@ -1495,7 +1501,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ "async-trait", - "bytes 1.7.2", + "bytes 1.8.0", "futures-util", "http 0.2.12", "http-body 0.4.6", @@ -1512,7 +1518,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9a320103719de37b7b4da4c8eb629d4573f6bcfd3dfe80d3208806895ccf81d" dependencies = [ "axum", - "bytes 1.7.2", + "bytes 1.8.0", "futures-util", "http 0.2.12", "mime", @@ -1535,7 +1541,7 @@ dependencies = [ "addr2line", "cfg-if", "libc", - "miniz_oxide 0.8.0", + "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", @@ -1583,9 +1589,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bigdecimal" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee" +checksum = "8f850665a0385e070b64c38d2354e6c104c8479c59868d1e48a0c13ee2c7a1c1" dependencies = [ "autocfg", "libm", @@ -1604,26 +1610,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bindgen" -version = "0.69.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" -dependencies = [ - "bitflags 2.6.0", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn 2.0.87", -] - [[package]] name = "bindgen" version = "0.70.1" @@ -1712,9 +1698,9 @@ dependencies = [ [[package]] name = "bitstream-io" -version = "2.5.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452" +checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" [[package]] name = "bitvec" @@ -1825,15 +1811,15 @@ dependencies = [ "async-channel 2.3.1", "async-task", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "piper", ] [[package]] name = "borsh" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" dependencies = [ "borsh-derive", "cfg_aliases 0.2.1", @@ -1841,16 +1827,15 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +checksum = "c2593a3b8b938bd68373196c9832f516be11fa487ef4ae745eb282e6a56a7244" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.87", - "syn_derive", ] [[package]] @@ -1868,20 +1853,20 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.9", "serde", ] [[package]] name = "built" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" +checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" [[package]] name = "bumpalo" @@ -1919,18 +1904,18 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.17.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" +checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", @@ -1961,9 +1946,9 @@ dependencies = [ [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "bytes-utils" @@ -1971,7 +1956,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "either", ] @@ -1984,7 +1969,7 @@ dependencies = [ "client", "collections", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "language", @@ -2007,10 +1992,10 @@ checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ "bitflags 2.6.0", "log", - "polling 3.7.3", - "rustix 0.38.35", + "polling 3.7.4", + "rustix 0.38.40", "slab", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2020,7 +2005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix 0.38.35", + "rustix 0.38.40", "wayland-backend", "wayland-client", ] @@ -2036,9 +2021,9 @@ dependencies = [ [[package]] name = "cap-fs-ext" -version = "3.2.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb23061fc1c4ead4e45ca713080fe768e6234e959f5a5c399c39eb41aa34e56e" +checksum = "e16619ada836f12897a72011fe99b03f0025b87a8dbbea4f3c9f89b458a23bf3" dependencies = [ "cap-primitives", "cap-std", @@ -2048,21 +2033,21 @@ dependencies = [ [[package]] name = "cap-net-ext" -version = "3.2.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83ae11f116bcbafc5327c6af250341db96b5930046732e1905f7dc65887e0e1" +checksum = "710b0eb776410a22c89a98f2f80b2187c2ac3a8206b99f3412332e63c9b09de0" dependencies = [ "cap-primitives", "cap-std", - "rustix 0.38.35", + "rustix 0.38.40", "smallvec", ] [[package]] name = "cap-primitives" -version = "3.2.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d00bd8d26c4270d950eaaa837387964a2089a1c3c349a690a1fa03221d29531" +checksum = "82fa6c3f9773feab88d844aa50035a33fb6e7e7426105d2f4bb7aadc42a5f89a" dependencies = [ "ambient-authority", "fs-set-times", @@ -2070,16 +2055,16 @@ dependencies = [ "io-lifetimes 2.0.3", "ipnet", "maybe-owned", - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.52.0", "winx", ] [[package]] name = "cap-rand" -version = "3.2.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbcb16a619d8b8211ed61f42bd290d2a1ac71277a69cf8417ec0996fa92f5211" +checksum = "53774d49369892b70184f8312e50c1b87edccb376691de4485b0ff554b27c36c" dependencies = [ "ambient-authority", "rand 0.8.5", @@ -2087,27 +2072,27 @@ dependencies = [ [[package]] name = "cap-std" -version = "3.2.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19eb8e3d71996828751c1ed3908a439639752ac6bdc874e41469ef7fc15fbd7f" +checksum = "7f71b70818556b4fe2a10c7c30baac3f5f45e973f49fc2673d7c75c39d0baf5b" dependencies = [ "cap-primitives", "io-extras", "io-lifetimes 2.0.3", - "rustix 0.38.35", + "rustix 0.38.40", ] [[package]] name = "cap-time-ext" -version = "3.2.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61142dc51e25b7acc970ca578ce2c3695eac22bbba46c1073f5f583e78957725" +checksum = "69dd48afa2363f746c93f961c211f6f099fb594a3446b8097bc5f79db51b6816" dependencies = [ "ambient-authority", "cap-primitives", "iana-time-zone", "once_cell", - "rustix 0.38.35", + "rustix 0.38.40", "winx", ] @@ -2131,7 +2116,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -2166,7 +2151,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" dependencies = [ "heck 0.4.1", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "proc-macro2", "quote", @@ -2179,9 +2164,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.15" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "jobserver", "libc", @@ -2239,7 +2224,7 @@ dependencies = [ "client", "clock", "collections", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "language", @@ -2349,9 +2334,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.24" +version = "4.5.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7db6eca8c205649e8d3ccd05aa5042b1800a784e56bc7c43524fde8abbfa9b" +checksum = "d9647a559c112175f17cf724dc72d3645680a883c58481332779192b0d8e7a01" dependencies = [ "clap", ] @@ -2370,9 +2355,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "cli" @@ -2403,17 +2388,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0875e527e299fc5f4faba42870bf199a39ab0bb2dbba1b8aef0a2151451130f" dependencies = [ "bstr", - "bytes 1.7.2", + "bytes 1.8.0", "clickhouse-derive", "clickhouse-rs-cityhash-sys", - "futures 0.3.30", + "futures 0.3.31", "hyper 0.14.31", "hyper-tls", "lz4", "sealed", "serde", "static_assertions", - "thiserror", + "thiserror 1.0.69", "tokio", "url", ] @@ -2446,13 +2431,13 @@ dependencies = [ "anyhow", "async-native-tls", "async-recursion 0.3.2", - "async-tungstenite", + "async-tungstenite 0.28.0", "chrono", "clock", "cocoa 0.26.0", "collections", "feature_flags", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "log", @@ -2474,7 +2459,7 @@ dependencies = [ "sysinfo", "telemetry_events", "text", - "thiserror", + "thiserror 1.0.69", "time", "tiny_http", "tokio-socks", @@ -2579,7 +2564,7 @@ dependencies = [ "assistant", "async-stripe", "async-trait", - "async-tungstenite", + "async-tungstenite 0.28.0", "audio", "aws-config", "aws-sdk-kinesis", @@ -2597,14 +2582,14 @@ dependencies = [ "collections", "context_servers", "ctor", - "dashmap 6.0.1", + "dashmap 6.1.0", "derive_more", "editor", "env_logger 0.11.5", "envy", "file_finder", "fs", - "futures 0.3.30", + "futures 0.3.31", "git", "git_hosting_providers", "google_ai", @@ -2657,7 +2642,7 @@ dependencies = [ "telemetry_events", "text", "theme", - "thiserror", + "thiserror 1.0.69", "time", "tokio", "toml 0.8.19", @@ -2685,7 +2670,7 @@ dependencies = [ "db", "editor", "emojis", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "gpui", "http_client", @@ -2732,9 +2717,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "combine" @@ -2742,7 +2727,7 @@ version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "memchr", ] @@ -2844,7 +2829,7 @@ dependencies = [ "anyhow", "collections", "command_palette_hooks", - "futures 0.3.30", + "futures 0.3.31", "gpui", "log", "parking_lot", @@ -2889,7 +2874,7 @@ dependencies = [ "command_palette_hooks", "editor", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "indoc", @@ -3022,11 +3007,11 @@ dependencies = [ [[package]] name = "coreaudio-sys" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f01585027057ff5f0a5bf276174ae4c1594a2c5bde93d5f46a016d76270f5a9" +checksum = "2ce857aa0b77d77287acc1ac3e37a05a8c95a2af3647d23b15f263bdaeb7562b" dependencies = [ - "bindgen 0.69.4", + "bindgen", ] [[package]] @@ -3085,9 +3070,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] @@ -3406,9 +3391,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", @@ -3561,7 +3546,7 @@ dependencies = [ "fuzzy-matcher", "shell-words", "tempfile", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -3624,6 +3609,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "dlib" version = "0.5.2" @@ -3670,9 +3666,9 @@ dependencies = [ [[package]] name = "dwrote" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da3498378ed373237bdef1eddcc64e7be2d3ba4841f4c22a998e81cadeea83c" +checksum = "70182709525a3632b2ba96b6569225467b18ecb4a77f46d255f713a6bebf05fd" dependencies = [ "lazy_static", "libc", @@ -3721,7 +3717,7 @@ dependencies = [ "emojis", "env_logger 0.11.5", "file_icons", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "git", "gpui", @@ -3767,18 +3763,6 @@ dependencies = [ "workspace", ] -[[package]] -name = "educe" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4bd92664bf78c4d3dba9b7cdafce6fa15b13ed3ed16175218196942e99168a8" -dependencies = [ - "enum-ordinalize", - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "either" version = "1.13.0" @@ -3822,9 +3806,9 @@ dependencies = [ [[package]] name = "embed-resource" -version = "2.4.3" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edcacde9351c33139a41e3c97eb2334351a81a2791bebb0b243df837128f602" +checksum = "b68b6f9f63a0b6a38bc447d4ce84e2b388f3ec95c99c641c8ff0dd3ef89a6379" dependencies = [ "cc", "memchr", @@ -3863,9 +3847,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -3876,26 +3860,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" -[[package]] -name = "enum-ordinalize" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" -dependencies = [ - "enum-ordinalize-derive", -] - -[[package]] -name = "enum-ordinalize-derive" -version = "4.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "enumflags2" version = "0.7.10" @@ -4117,15 +4081,14 @@ dependencies = [ [[package]] name = "exr" -version = "1.72.0" +version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" dependencies = [ "bit_field", - "flume", "half", "lebe", - "miniz_oxide 0.7.4", + "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", @@ -4141,7 +4104,7 @@ dependencies = [ "async-trait", "collections", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "language", @@ -4193,7 +4156,7 @@ dependencies = [ "env_logger 0.11.5", "extension", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "language", @@ -4282,8 +4245,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ "bit-set 0.8.0", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -4303,9 +4266,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fd-lock" @@ -4314,15 +4277,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" dependencies = [ "cfg-if", - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.52.0", ] [[package]] name = "fdeflate" -version = "0.3.4" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" dependencies = [ "simd-adler32", ] @@ -4331,7 +4294,7 @@ dependencies = [ name = "feature_flags" version = "0.1.0" dependencies = [ - "futures 0.3.30", + "futures 0.3.31", "gpui", ] @@ -4344,7 +4307,7 @@ dependencies = [ "client", "db", "editor", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "human_bytes", @@ -4385,7 +4348,7 @@ dependencies = [ "editor", "env_logger 0.11.5", "file_icons", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "gpui", "language", @@ -4423,7 +4386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" dependencies = [ "libc", - "thiserror", + "thiserror 1.0.69", "winapi", ] @@ -4447,12 +4410,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -4485,6 +4448,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "font-kit" version = "0.14.1" @@ -4511,9 +4480,9 @@ dependencies = [ [[package]] name = "font-types" -version = "0.6.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0189ccb084f77c5523e08288d418cbaa09c451a08515678a0aa265df9a8b60" +checksum = "b3971f9a5ca983419cdc386941ba3b9e1feba01a0ab888adf78739feb2798492" dependencies = [ "bytemuck", ] @@ -4623,7 +4592,7 @@ dependencies = [ "cocoa 0.26.0", "collections", "fsevent", - "futures 0.3.30", + "futures 0.3.31", "git", "git2", "gpui", @@ -4650,7 +4619,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "033b337d725b97690d86893f9de22b67b80dcc4e9ad815f348254c38119db8fb" dependencies = [ "io-lifetimes 2.0.3", - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.52.0", ] @@ -4707,9 +4676,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -4726,16 +4695,16 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f444c45a1cb86f2a7e301469fd50a82084a60dadc25d94529a8312276ecb71a" dependencies = [ - "futures 0.3.30", + "futures 0.3.31", "futures-timer", "pin-utils", ] [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -4743,15 +4712,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -4771,9 +4740,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -4792,11 +4761,11 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ - "fastrand 2.1.1", + "fastrand 2.2.0", "futures-core", "futures-io", "parking", @@ -4805,9 +4774,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -4816,15 +4785,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" @@ -4834,9 +4803,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures 0.1.31", "futures-channel", @@ -4930,15 +4899,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" dependencies = [ "fallible-iterator", - "indexmap 2.4.0", + "indexmap 2.6.0", "stable_deref_trait", ] [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git" @@ -4987,7 +4956,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "futures 0.3.30", + "futures 0.3.31", "git", "gpui", "http_client", @@ -5015,15 +4984,15 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] name = "gloo-timers" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", @@ -5033,9 +5002,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f865cbd94bd355b89611211e49508da98a1fce0ad755c1e8448fb96711b24528" +checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483" dependencies = [ "js-sys", "slotmap", @@ -5073,7 +5042,7 @@ name = "google_ai" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.30", + "futures 0.3.31", "http_client", "schemars", "serde", @@ -5120,7 +5089,7 @@ dependencies = [ "ashpd", "async-task", "backtrace", - "bindgen 0.70.1", + "bindgen", "blade-graphics", "blade-macros", "blade-util", @@ -5145,7 +5114,7 @@ dependencies = [ "flume", "font-kit", "foreign-types 0.5.0", - "futures 0.3.30", + "futures 0.3.31", "gpui_macros", "http_client", "image", @@ -5179,7 +5148,7 @@ dependencies = [ "strum 0.25.0", "sum_tree", "taffy", - "thiserror", + "thiserror 1.0.69", "unicode-segmentation", "usvg", "util", @@ -5230,13 +5199,13 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "fnv", "futures-core", "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.4.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -5250,12 +5219,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", - "bytes 1.7.2", + "bytes 1.8.0", "fnv", "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.4.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -5283,7 +5252,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5298,7 +5267,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -5330,6 +5299,17 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashlink" version = "0.8.4" @@ -5355,7 +5335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ "base64 0.21.7", - "bytes 1.7.2", + "bytes 1.8.0", "headers-core", "http 0.2.12", "httpdate", @@ -5534,7 +5514,7 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "fnv", "itoa", ] @@ -5545,7 +5525,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "fnv", "itoa", ] @@ -5556,7 +5536,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "http 0.2.12", "pin-project-lite", ] @@ -5567,7 +5547,7 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "http 1.1.0", ] @@ -5577,7 +5557,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-util", "http 1.1.0", "http-body 1.0.1", @@ -5616,9 +5596,9 @@ name = "http_client" version = "0.1.0" dependencies = [ "anyhow", - "bytes 1.7.2", + "bytes 1.8.0", "derive_more", - "futures 0.3.30", + "futures 0.3.31", "http 1.1.0", "log", "serde", @@ -5628,9 +5608,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -5656,7 +5636,7 @@ version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-channel", "futures-core", "futures-util", @@ -5676,11 +5656,11 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-channel", "futures-util", "h2 0.4.6", @@ -5718,9 +5698,9 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", - "rustls 0.23.13", + "rustls 0.23.16", "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", @@ -5734,7 +5714,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "hyper 0.14.31", "native-tls", "tokio", @@ -5743,16 +5723,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.4.1", + "hyper 1.5.0", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -5762,9 +5742,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -5783,6 +5763,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "id-arena" version = "2.2.1" @@ -5791,12 +5889,23 @@ checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -5809,7 +5918,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.9", "same-file", "walkdir", "winapi-util", @@ -5817,9 +5926,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.2" +version = "0.25.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" dependencies = [ "bytemuck", "byteorder-lite", @@ -5840,9 +5949,9 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904" +checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f" dependencies = [ "byteorder-lite", "quick-error", @@ -5872,9 +5981,9 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" [[package]] name = "imgref" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126" +checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexed_docs" @@ -5887,7 +5996,7 @@ dependencies = [ "derive_more", "extension", "fs", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "gpui", "heed", @@ -5916,12 +6025,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.1", "serde", ] @@ -5956,7 +6065,7 @@ dependencies = [ "copilot", "editor", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "indoc", "language", @@ -6035,9 +6144,9 @@ dependencies = [ [[package]] name = "io-extras" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9f046b9af244f13b3bd939f55d16830ac3a201e8a9ba9661bfcb03e2be72b9b" +checksum = "7d45fd7584f9b67ac37bc041212d06bfac0700b36456b05890d36a3b626260eb" dependencies = [ "io-lifetimes 2.0.3", "windows-sys 0.52.0", @@ -6090,9 +6199,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-docker" @@ -6174,7 +6283,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -6218,9 +6327,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -6241,15 +6350,51 @@ dependencies = [ ] [[package]] -name = "jupyter-serde" -version = "0.4.0" +name = "jupyter-protocol" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd71aa17c4fa65e6d7536ab2728881a41f8feb2ee5841c2240516c3c3d65d8b3" +checksum = "5f3e9d36f282f7e0400de20921d283121a97c5a5a6db2c1bb0c0853defff9934" +dependencies = [ + "anyhow", + "async-trait", + "bytes 1.8.0", + "chrono", + "futures 0.3.31", + "jupyter-serde", + "rand 0.8.5", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "jupyter-serde" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11adb69edaf2eb03d5e84249f68f870dd03d4c8f955314b5a32b2db5798e9b9a" dependencies = [ "anyhow", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "jupyter-websocket-client" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d315d037789a652084877b0919615e937d2f2e877b01aa4ba8fcc1ab07cb58b" +dependencies = [ + "anyhow", + "async-trait", + "async-tungstenite 0.22.2", + "futures 0.3.31", + "jupyter-protocol", + "jupyter-serde", + "serde", + "serde_json", + "url", "uuid", ] @@ -6285,9 +6430,9 @@ dependencies = [ [[package]] name = "kurbo" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5aa9f0f96a938266bdb12928a67169e8d22c6a786fda8ed984b85e6ba93c3c" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" dependencies = [ "arrayvec", "smallvec", @@ -6314,7 +6459,7 @@ dependencies = [ "ctor", "ec4rs", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "git", "globset", @@ -6372,7 +6517,7 @@ dependencies = [ "editor", "env_logger 0.11.5", "feature_flags", - "futures 0.3.30", + "futures 0.3.31", "google_ai", "gpui", "http_client", @@ -6396,7 +6541,7 @@ dependencies = [ "telemetry_events", "text", "theme", - "thiserror", + "thiserror 1.0.69", "tiktoken-rs", "ui", "unindent", @@ -6429,7 +6574,7 @@ dependencies = [ "copilot", "editor", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "gpui", "itertools 0.13.0", "language", @@ -6455,7 +6600,7 @@ dependencies = [ "async-tar", "async-trait", "collections", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "language", @@ -6512,12 +6657,6 @@ dependencies = [ "spin", ] -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "leb128" version = "0.2.5" @@ -6548,13 +6687,12 @@ dependencies = [ [[package]] name = "libfuzzer-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" dependencies = [ "arbitrary", "cc", - "once_cell", ] [[package]] @@ -6581,9 +6719,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libmimalloc-sys" @@ -6603,7 +6741,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.3", + "redox_syscall 0.5.7", ] [[package]] @@ -6670,6 +6808,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "live_kit_client" version = "0.1.0" @@ -6679,7 +6823,7 @@ dependencies = [ "async-trait", "collections", "core-foundation 0.9.4", - "futures 0.3.30", + "futures 0.3.31", "gpui", "live_kit_server", "log", @@ -6750,11 +6894,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.1", ] [[package]] @@ -6766,7 +6910,7 @@ dependencies = [ "collections", "ctor", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "gpui", "log", "lsp-types", @@ -6795,19 +6939,18 @@ dependencies = [ [[package]] name = "lz4" -version = "1.26.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958b4caa893816eea05507c20cfe47574a43d9a697138a7872990bba8a0ece68" +checksum = "4d1febb2b4a79ddd1980eede06a8f7902197960aa0383ffcfdd62fe723036725" dependencies = [ - "libc", "lz4-sys", ] [[package]] name = "lz4-sys" -version = "1.10.0" +version = "1.11.1+lz4-1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109de74d5d2353660401699a4174a4ff23fcc649caf553df71933c7fb45ad868" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" dependencies = [ "cc", "libc", @@ -6850,7 +6993,7 @@ dependencies = [ "anyhow", "assets", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "gpui", "language", "languages", @@ -6938,6 +7081,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", + "rayon", ] [[package]] @@ -6991,7 +7135,7 @@ name = "media" version = "0.1.0" dependencies = [ "anyhow", - "bindgen 0.70.1", + "bindgen", "core-foundation 0.9.4", "foreign-types 0.5.0", "metal", @@ -7010,14 +7154,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.35", + "rustix 0.38.40", ] [[package]] name = "memmap2" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] @@ -7085,16 +7229,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", - "simd-adler32", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -7102,6 +7236,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -7162,7 +7297,7 @@ dependencies = [ "collections", "ctor", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "gpui", "itertools 0.13.0", "language", @@ -7196,12 +7331,12 @@ dependencies = [ "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "rustc-hash 1.1.0", "spirv", "termcolor", - "thiserror", + "thiserror 1.0.69", "unicode-xid", ] @@ -7242,16 +7377,16 @@ dependencies = [ [[package]] name = "nbformat" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9ffb2ca556072f114bcaf2ca01dde7f1bc8a4946097dd804cb5a22d8af7d6df" +checksum = "187de1b1f1430353ef9b5208096d84f7bf089ee1593f14213d122b7fbb1f3dee" dependencies = [ "anyhow", "chrono", "jupyter-serde", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "uuid", ] @@ -7266,7 +7401,7 @@ dependencies = [ "log", "ndk-sys", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -7314,7 +7449,7 @@ dependencies = [ "async-trait", "async-watch", "async_zip", - "futures 0.3.30", + "futures 0.3.31", "http_client", "log", "paths", @@ -7600,7 +7735,7 @@ version = "0.8.0-pre" source = "git+https://github.com/KillTheMule/nvim-rs?branch=master#69500bae73b8b3f02a05b7bee621a0d0e633da6c" dependencies = [ "async-trait", - "futures 0.3.30", + "futures 0.3.31", "log", "parity-tokio-ipc", "rmp", @@ -7621,13 +7756,13 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "crc32fast", - "hashbrown 0.14.5", - "indexmap 2.4.0", + "hashbrown 0.15.1", + "indexmap 2.6.0", "memchr", ] @@ -7659,7 +7794,7 @@ name = "ollama" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.30", + "futures 0.3.31", "http_client", "schemars", "serde", @@ -7668,9 +7803,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oo7" @@ -7680,7 +7815,7 @@ checksum = "8fc6ce4692fbfd044ce22ca07dcab1a30fa12432ca2aa5b1294eca50d3332a24" dependencies = [ "aes", "async-fs 2.1.2", - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", "async-net 2.0.0", "blocking", @@ -7688,7 +7823,7 @@ dependencies = [ "cipher", "digest", "endi", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "futures-util", "hkdf", "hmac", @@ -7713,9 +7848,9 @@ checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" [[package]] name = "open" -version = "5.3.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" +checksum = "3ecd52f0b8d15c40ce4820aa251ed5de032e5d91fab27f7db2f40d42a8bdf69c" dependencies = [ "is-wsl", "libc", @@ -7727,7 +7862,7 @@ name = "open_ai" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.30", + "futures 0.3.31", "http_client", "schemars", "serde", @@ -7749,9 +7884,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -7781,18 +7916,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.3.1+3.3.1" +version = "300.4.0+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" +checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -7978,7 +8113,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" dependencies = [ - "futures 0.3.30", + "futures 0.3.31", "libc", "log", "rand 0.7.3", @@ -8010,7 +8145,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -8110,20 +8245,20 @@ dependencies = [ [[package]] name = "pest" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror", + "thiserror 1.0.69", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -8131,9 +8266,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", @@ -8144,9 +8279,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -8520,7 +8655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.4.0", + "indexmap 2.6.0", ] [[package]] @@ -8618,18 +8753,18 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", @@ -8638,9 +8773,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -8655,7 +8790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.1.1", + "fastrand 2.2.0", "futures-io", ] @@ -8692,9 +8827,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "plist" @@ -8703,7 +8838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" dependencies = [ "base64 0.22.1", - "indexmap 2.4.0", + "indexmap 2.6.0", "quick-xml 0.32.0", "serde", "time", @@ -8711,9 +8846,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -8724,30 +8859,30 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "png" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.7.4", + "miniz_oxide", ] [[package]] @@ -8768,15 +8903,15 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.3" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.35", + "rustix 0.38.40", "tracing", "windows-sys 0.59.0", ] @@ -8795,13 +8930,13 @@ checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" dependencies = [ "atomic", "crossbeam-queue", - "futures 0.3.30", + "futures 0.3.31", "log", "parking_lot", "pin-project", "pollster", "static_assertions", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -8868,9 +9003,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", "syn 2.0.87", @@ -8882,7 +9017,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.22.20", + "toml_edit 0.22.22", ] [[package]] @@ -8909,6 +9044,28 @@ dependencies = [ "version_check", ] +[[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 2.0.87", +] + [[package]] name = "proc-macro2" version = "1.0.89" @@ -8950,7 +9107,7 @@ dependencies = [ "env_logger 0.11.5", "fancy-regex 0.14.0", "fs", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "git", "git2", @@ -9034,7 +9191,7 @@ version = "0.1.0" dependencies = [ "anyhow", "editor", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "gpui", "language", @@ -9062,7 +9219,7 @@ dependencies = [ "memchr", "parking_lot", "protobuf", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9071,7 +9228,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "prost-derive", ] @@ -9081,7 +9238,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "heck 0.3.3", "itertools 0.10.5", "lazy_static", @@ -9114,7 +9271,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "prost", ] @@ -9137,9 +9294,9 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "psm" -version = "0.1.21" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" dependencies = [ "cc", ] @@ -9219,9 +9376,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.34.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f24d770aeca0eacb81ac29dfbc55ebcc09312fdd1f8bbecdc7e4a84e000e3b4" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", ] @@ -9246,45 +9403,49 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash 2.0.0", - "rustls 0.23.13", + "rustls 0.23.16", "socket2 0.5.7", - "thiserror", + "thiserror 2.0.3", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", + "getrandom 0.2.15", "rand 0.8.5", "ring", "rustc-hash 2.0.0", - "rustls 0.23.13", + "rustls 0.23.16", + "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.3", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" dependencies = [ + "cfg_aliases 0.2.1", "libc", "once_cell", "socket2 0.5.7", @@ -9414,22 +9575,23 @@ dependencies = [ "rand_chacha 0.3.1", "simd_helpers", "system-deps", - "thiserror", + "thiserror 1.0.69", "v_frame", "wasm-bindgen", ] [[package]] name = "ravif" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd" +checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", + "rayon", "rgb", ] @@ -9473,9 +9635,9 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.20.0" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c141b9980e1150201b2a3a32879001c8f975fe313ec3df5471a9b5c79a880cd" +checksum = "4a04b892cb6f91951f144c33321843790c8574c825aafdb16d815fd7183b5229" dependencies = [ "bytemuck", "font-types", @@ -9489,7 +9651,7 @@ dependencies = [ "auto_update", "editor", "file_finder", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "gpui", "itertools 0.13.0", @@ -9526,18 +9688,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -9550,7 +9703,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -9575,14 +9728,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -9596,13 +9749,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -9619,9 +9772,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "release_channel" @@ -9639,7 +9792,7 @@ dependencies = [ "async-trait", "collections", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "itertools 0.13.0", "log", @@ -9653,7 +9806,7 @@ dependencies = [ "shlex", "smol", "tempfile", - "thiserror", + "thiserror 1.0.69", "util", "which 6.0.3", ] @@ -9673,7 +9826,7 @@ dependencies = [ "env_logger 0.11.5", "fork", "fs", - "futures 0.3.30", + "futures 0.3.31", "git", "git_hosting_providers", "gpui", @@ -9727,11 +9880,13 @@ dependencies = [ "editor", "env_logger 0.11.5", "feature_flags", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "image", "indoc", + "jupyter-protocol", + "jupyter-websocket-client", "language", "languages", "log", @@ -9767,7 +9922,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", - "bytes 1.7.2", + "bytes 1.8.0", "encoding_rs", "futures-core", "futures-util", @@ -9806,7 +9961,7 @@ version = "0.12.8" source = "git+https://github.com/zed-industries/reqwest.git?rev=fd110f6998da16bbca97b6dddda9be7827c50e29#fd110f6998da16bbca97b6dddda9be7827c50e29" dependencies = [ "base64 0.22.1", - "bytes 1.7.2", + "bytes 1.8.0", "encoding_rs", "futures-core", "futures-util", @@ -9814,7 +9969,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-rustls 0.27.3", "hyper-util", "ipnet", @@ -9825,9 +9980,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.13", + "rustls 0.23.16", "rustls-native-certs 0.8.0", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", @@ -9852,8 +10007,8 @@ name = "reqwest_client" version = "0.1.0" dependencies = [ "anyhow", - "bytes 1.7.2", - "futures 0.3.30", + "bytes 1.8.0", + "futures 0.3.31", "gpui", "http_client", "log", @@ -9890,9 +10045,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.49" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cd5a1e95672f201913966f39baf355b53b5d92833431847295ae0346a5b939" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" dependencies = [ "bytemuck", ] @@ -9901,7 +10056,7 @@ dependencies = [ name = "rich_text" version = "0.1.0" dependencies = [ - "futures 0.3.30", + "futures 0.3.31", "gpui", "language", "linkify", @@ -9934,7 +10089,7 @@ checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", - "bytes 1.7.2", + "bytes 1.8.0", "hashbrown 0.12.3", "ptr_meta", "rend", @@ -9984,7 +10139,7 @@ checksum = "6006a627c1a38d37f3d3a85c6575418cfe34a5392d60a686d0071e1c8d427acb" dependencies = [ "cpal", "hound", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -10016,12 +10171,12 @@ name = "rpc" version = "0.1.0" dependencies = [ "anyhow", - "async-tungstenite", + "async-tungstenite 0.28.0", "base64 0.22.1", "chrono", "collections", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "gpui", "parking_lot", "proto", @@ -10058,22 +10213,22 @@ dependencies = [ [[package]] name = "runtimelib" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe23ba9967355bbb1be2fb9a8e51bd239ffdf9c791fad5a9b765122ee2bde2e4" +checksum = "2db079f82c110e25c3202d20c7cd29dcbfa93d96de7c5bb8bb6f294f477567cf" dependencies = [ "anyhow", "async-dispatcher", "async-std", "base64 0.22.1", - "bytes 1.7.2", + "bytes 1.8.0", "chrono", "data-encoding", "dirs 5.0.1", - "futures 0.3.30", + "futures 0.3.31", "glob", + "jupyter-protocol", "jupyter-serde", - "rand 0.8.5", "ring", "serde", "serde_json", @@ -10126,7 +10281,7 @@ checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", "borsh", - "bytes 1.7.2", + "bytes 1.8.0", "num-traits", "rand 0.8.5", "rkyv", @@ -10177,9 +10332,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags 2.6.0", "errno 0.3.9", @@ -10198,7 +10353,7 @@ checksum = "a25c3aad9fc1424eb82c88087789a7d938e1829724f3e4043163baf0d13cfc12" dependencies = [ "errno 0.3.9", "libc", - "rustix 0.38.35", + "rustix 0.38.40", ] [[package]] @@ -10215,9 +10370,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" dependencies = [ "once_cell", "ring", @@ -10246,7 +10401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", "security-framework", @@ -10263,19 +10418,21 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] [[package]] name = "rustls-webpki" @@ -10300,9 +10457,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "rustybuzz" @@ -10347,11 +10504,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -10415,12 +10572,12 @@ dependencies = [ [[package]] name = "sea-bae" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd3534a9978d0aa7edd2808dc1f8f31c4d0ecd31ddf71d997b3c98e9f3c9114" +checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" dependencies = [ "heck 0.4.1", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.87", @@ -10436,7 +10593,7 @@ dependencies = [ "async-trait", "bigdecimal", "chrono", - "futures 0.3.30", + "futures 0.3.31", "log", "ouroboros", "rust_decimal", @@ -10447,7 +10604,7 @@ dependencies = [ "serde_json", "sqlx", "strum 0.26.3", - "thiserror", + "thiserror 1.0.69", "time", "tracing", "url", @@ -10456,9 +10613,9 @@ dependencies = [ [[package]] name = "sea-orm-macros" -version = "1.1.0-rc.1" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07aadcb2ee9fad78a3bf74f6430ba94865ab4d8ad237f978e99dafa97ee0df57" +checksum = "3a239e3bb1b566ad4ec2654d0d193d6ceddfd733487edc9c21a64d214c773910" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -10470,13 +10627,12 @@ dependencies = [ [[package]] name = "sea-query" -version = "0.32.0-rc.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fba498acd58ce434669f273505cd07737065472eb541c3f813c7f4ce33993f5" +checksum = "ff504d13b5e4b52fffcf2fb203d0352a5722fa5151696db768933e41e1e591bb" dependencies = [ "bigdecimal", "chrono", - "educe", "inherent", "ordered-float 3.9.2", "rust_decimal", @@ -10487,9 +10643,9 @@ dependencies = [ [[package]] name = "sea-query-binder" -version = "0.7.0-rc.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc3296903e60ddc7c9f4601cd6ef31a4b1584bf22480587e00b9ef743071b57" +checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608" dependencies = [ "bigdecimal", "chrono", @@ -10529,7 +10685,7 @@ dependencies = [ "client", "collections", "editor", - "futures 0.3.30", + "futures 0.3.31", "gpui", "language", "menu", @@ -10574,9 +10730,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -10601,7 +10757,7 @@ dependencies = [ "env_logger 0.11.5", "feature_flags", "fs", - "futures 0.3.30", + "futures 0.3.31", "futures-batch", "gpui", "heed", @@ -10648,18 +10804,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -10703,7 +10859,7 @@ version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "itoa", "memchr", "ryu", @@ -10712,12 +10868,13 @@ dependencies = [ [[package]] name = "serde_json_lenient" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d0bae483150302560d7cb52e7932f39b69a6fbdd099e48d33ef060a8c9c078" +checksum = "2bf0c7e21364d0e199dd2f6c339ca18d6fca75b69458a247e8b27ff1c92f5b86" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "itoa", + "memchr", "ryu", "serde", ] @@ -10740,7 +10897,7 @@ checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -10751,7 +10908,7 @@ checksum = "8cac3f1e2ca2fe333923a1ae72caca910b98ed0630bb35ef6f8c8517d6e81afa" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -10767,9 +10924,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -10805,7 +10962,7 @@ dependencies = [ "collections", "ec4rs", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "indoc", "log", @@ -10962,9 +11119,9 @@ dependencies = [ [[package]] name = "simdutf8" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "similar" @@ -10980,7 +11137,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 1.0.69", "time", ] @@ -11018,9 +11175,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "skrifa" -version = "0.20.0" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abea4738067b1e628c6ce28b2c216c19e9ea95715cdb332680e821c3bec2ef23" +checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe" dependencies = [ "bytemuck", "read-fonts", @@ -11118,7 +11275,7 @@ dependencies = [ "anyhow", "collections", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "parking_lot", "paths", @@ -11164,9 +11321,9 @@ dependencies = [ [[package]] name = "spdx" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47317bbaf63785b53861e1ae2d11b80d6b624211d42cb20efcd210ee6f8a14bc" +checksum = "bae30cc7bfe3656d60ee99bf6836f472b0c53dddcbf335e253329abb16e535a2" dependencies = [ "smallvec", ] @@ -11221,7 +11378,7 @@ version = "0.1.0" dependencies = [ "anyhow", "collections", - "futures 0.3.30", + "futures 0.3.31", "indoc", "libsqlite3-sys", "parking_lot", @@ -11273,7 +11430,7 @@ dependencies = [ "atoi", "bigdecimal", "byteorder", - "bytes 1.7.2", + "bytes 1.8.0", "chrono", "crc", "crossbeam-queue", @@ -11287,7 +11444,7 @@ dependencies = [ "hashbrown 0.14.5", "hashlink 0.9.1", "hex", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "memchr", "once_cell", @@ -11301,7 +11458,7 @@ dependencies = [ "sha2", "smallvec", "sqlformat", - "thiserror", + "thiserror 1.0.69", "time", "tokio", "tokio-stream", @@ -11361,7 +11518,7 @@ dependencies = [ "bigdecimal", "bitflags 2.6.0", "byteorder", - "bytes 1.7.2", + "bytes 1.8.0", "chrono", "crc", "digest", @@ -11390,7 +11547,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 1.0.69", "time", "tracing", "uuid", @@ -11434,7 +11591,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 1.0.69", "time", "tracing", "uuid", @@ -11624,7 +11781,7 @@ dependencies = [ "collections", "editor", "env_logger 0.11.5", - "futures 0.3.30", + "futures 0.3.31", "gpui", "http_client", "language", @@ -11649,7 +11806,7 @@ name = "supermaven_api" version = "0.1.0" dependencies = [ "anyhow", - "futures 0.3.30", + "futures 0.3.31", "http_client", "paths", "serde", @@ -11659,15 +11816,15 @@ dependencies = [ [[package]] name = "sval" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53eb957fbc79a55306d5d25d87daf3627bc3800681491cda0709eef36c748bfe" +checksum = "f6dc0f9830c49db20e73273ffae9b5240f63c42e515af1da1fceefb69fceafd8" [[package]] name = "sval_buffer" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96e860aef60e9cbf37888d4953a13445abf523c534640d1f6174d310917c410d" +checksum = "429922f7ad43c0ef8fd7309e14d750e38899e32eb7e8da656ea169dd28ee212f" dependencies = [ "sval", "sval_ref", @@ -11675,18 +11832,18 @@ dependencies = [ [[package]] name = "sval_dynamic" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3f2b07929a1127d204ed7cb3905049381708245727680e9139dac317ed556f" +checksum = "68f16ff5d839396c11a30019b659b0976348f3803db0626f736764c473b50ff4" dependencies = [ "sval", ] [[package]] name = "sval_fmt" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e188677497de274a1367c4bda15bd2296de4070d91729aac8f0a09c1abf64d" +checksum = "c01c27a80b6151b0557f9ccbe89c11db571dc5f68113690c1e028d7e974bae94" dependencies = [ "itoa", "ryu", @@ -11695,9 +11852,9 @@ dependencies = [ [[package]] name = "sval_json" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f456c07dae652744781f2245d5e3b78e6a9ebad70790ac11eb15dbdbce5282" +checksum = "0deef63c70da622b2a8069d8600cf4b05396459e665862e7bdb290fd6cf3f155" dependencies = [ "itoa", "ryu", @@ -11706,9 +11863,9 @@ dependencies = [ [[package]] name = "sval_nested" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "886feb24709f0476baaebbf9ac10671a50163caa7e439d7a7beb7f6d81d0a6fb" +checksum = "a39ce5976ae1feb814c35d290cf7cf8cd4f045782fe1548d6bc32e21f6156e9f" dependencies = [ "sval", "sval_buffer", @@ -11717,18 +11874,18 @@ dependencies = [ [[package]] name = "sval_ref" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2e7fc517d778f44f8cb64140afa36010999565528d48985f55e64d45f369ce" +checksum = "bb7c6ee3751795a728bc9316a092023529ffea1783499afbc5c66f5fabebb1fa" dependencies = [ "sval", ] [[package]] name = "sval_serde" -version = "2.13.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79bf66549a997ff35cd2114a27ac4b0c2843280f2cfa84b240d169ecaa0add46" +checksum = "2a5572d0321b68109a343634e3a5d576bf131b82180c6c442dee06349dfc652a" dependencies = [ "serde", "sval", @@ -11737,9 +11894,9 @@ dependencies = [ [[package]] name = "svg_fmt" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20e16a0f46cf5fd675563ef54f26e83e20f2366bcf027bcb3cc3ed2b98aaf2ca" +checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" [[package]] name = "svgtypes" @@ -11753,9 +11910,9 @@ dependencies = [ [[package]] name = "swash" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93cdc334a50fcc2aa3f04761af3b28196280a6aaadb1ef11215c478ae32615ac" +checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2" dependencies = [ "skrifa", "yazi", @@ -11784,18 +11941,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "sync_wrapper" version = "0.1.2" @@ -11820,6 +11965,17 @@ dependencies = [ "crossbeam-queue", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "sys-locale" version = "0.3.2" @@ -11840,7 +11996,7 @@ dependencies = [ "memchr", "ntapi", "rayon", - "windows 0.54.0", + "windows 0.57.0", ] [[package]] @@ -11909,7 +12065,7 @@ dependencies = [ "cap-std", "fd-lock", "io-lifetimes 2.0.3", - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.52.0", "winx", ] @@ -11974,7 +12130,7 @@ version = "0.1.0" dependencies = [ "anyhow", "collections", - "futures 0.3.30", + "futures 0.3.31", "gpui", "hex", "parking_lot", @@ -12021,14 +12177,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.1.1", + "fastrand 2.2.0", "once_cell", - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.59.0", ] @@ -12060,7 +12216,7 @@ dependencies = [ "anyhow", "collections", "dirs 4.0.0", - "futures 0.3.30", + "futures 0.3.31", "gpui", "libc", "rand 0.8.5", @@ -12074,7 +12230,7 @@ dependencies = [ "sysinfo", "task", "theme", - "thiserror", + "thiserror 1.0.69", "util", "windows 0.58.0", ] @@ -12085,7 +12241,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ - "rustix 0.38.35", + "rustix 0.38.40", "windows-sys 0.59.0", ] @@ -12099,7 +12255,7 @@ dependencies = [ "db", "dirs 4.0.0", "editor", - "futures 0.3.30", + "futures 0.3.31", "gpui", "itertools 0.13.0", "language", @@ -12151,7 +12307,7 @@ dependencies = [ "collections", "derive_more", "fs", - "futures 0.3.30", + "futures 0.3.31", "gpui", "indexmap 1.9.3", "log", @@ -12215,7 +12371,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl 2.0.3", ] [[package]] @@ -12229,6 +12394,17 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -12356,6 +12532,16 @@ dependencies = [ "url", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -12419,12 +12605,12 @@ dependencies = [ [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", - "bytes 1.7.2", + "bytes 1.8.0", "libc", "mio 1.0.2", "parking_lot", @@ -12483,7 +12669,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.13", + "rustls 0.23.16", "rustls-pki-types", "tokio", ] @@ -12497,15 +12683,15 @@ dependencies = [ "either", "futures-io", "futures-util", - "thiserror", + "thiserror 1.0.69", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -12542,7 +12728,7 @@ version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-core", "futures-io", "futures-sink", @@ -12580,7 +12766,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit 0.22.22", ] [[package]] @@ -12598,7 +12784,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", @@ -12607,15 +12793,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.4.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow 0.6.20", ] [[package]] @@ -12662,7 +12848,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ "bitflags 1.3.2", - "bytes 1.7.2", + "bytes 1.8.0", "futures-core", "futures-util", "http 0.2.12", @@ -12680,7 +12866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ "bitflags 2.6.0", - "bytes 1.7.2", + "bytes 1.8.0", "futures-core", "futures-util", "http 0.2.12", @@ -12781,22 +12967,22 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20f4cd3642c47a85052a887d86704f4eac272969f61b686bdd3f772122aabaff" +checksum = "0203df02a3b6dd63575cc1d6e609edc2181c9a11867a271b25cfd2abff3ec5ca" dependencies = [ "cc", "regex", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "tree-sitter-language", "wasmtime-c-api-impl", ] [[package]] name = "tree-sitter-bash" -version = "0.23.1" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa5e1c6bd02c0053f3f68edcf5d8866b38a8640584279e30fca88149ce14dda" +checksum = "329a4d48623ac337d42b1df84e81a1c9dbb2946907c102ca72db158c1964a52e" dependencies = [ "cc", "tree-sitter-language", @@ -12814,9 +13000,9 @@ dependencies = [ [[package]] name = "tree-sitter-cpp" -version = "0.23.1" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d67e862242878d6ee50e1e5814f267ee3eea0168aea2cdbd700ccfb4c74b6d3" +checksum = "df2196ea9d47b4ab4a31b9297eaa5a5d19a0b121dceb9f118f6790ad0ab94743" dependencies = [ "cc", "tree-sitter-language", @@ -12824,9 +13010,9 @@ dependencies = [ [[package]] name = "tree-sitter-css" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d0018d6b1692a806f9cddaa1e5616951fd58840c39a0b21401b55ab3df12292" +checksum = "25435a275adb3226b6fddab891bbc50d1a500774a44ceb97022a39666ccda75d" dependencies = [ "cc", "tree-sitter-language", @@ -12854,9 +13040,9 @@ dependencies = [ [[package]] name = "tree-sitter-embedded-template" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9644d7586ebe850c84037ee2f4804dda4a9348eef053be6b1e0d7712342a2495" +checksum = "790063ef14e5b67556abc0b3be0ed863fb41d65ee791cf8c0b20eb42a1fa46af" dependencies = [ "cc", "tree-sitter-language", @@ -12864,9 +13050,9 @@ dependencies = [ [[package]] name = "tree-sitter-go" -version = "0.23.1" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf57626e4c9b6d6efaf8a8d5ee1241c5f178ae7bfdf693713ae6a774f01424e" +checksum = "dc4ee804a89f5c0e606b0b20579c86afc7cd0174aebd45c33b6b9c6237bcd97d" dependencies = [ "cc", "tree-sitter-language", @@ -12911,9 +13097,9 @@ dependencies = [ [[package]] name = "tree-sitter-jsdoc" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c4049eb0ad690e34e5f63640f75ce12a2ff8ba18344d0a13926805b139c0c8" +checksum = "a3862dfcb1038fc5e7812d7df14190afdeb7e1415288fd5f51f58395f8cb0faf" dependencies = [ "cc", "tree-sitter-language", @@ -12931,9 +13117,9 @@ dependencies = [ [[package]] name = "tree-sitter-language" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2545046bd1473dac6c626659cc2567c6c0ff302fc8b84a56c4243378276f7f57" +checksum = "e8ddffe35a0e5eeeadf13ff7350af564c6e73993a24db62caee1822b185c2600" [[package]] name = "tree-sitter-md" @@ -12946,9 +13132,9 @@ dependencies = [ [[package]] name = "tree-sitter-python" -version = "0.23.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65661b1a3e24139e2e54207e47d910ab07e28790d78efc7d5dc3a11ce2a110eb" +checksum = "2416de7eea3f2e1bd53c250f2d3f3394fc77f78497680f37f4b87918b8d752e3" dependencies = [ "cc", "tree-sitter-language", @@ -12966,9 +13152,9 @@ dependencies = [ [[package]] name = "tree-sitter-ruby" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ec5ee842e27791e0adffa0b2a177614de51d2a26e5c7e84d014ed7f097e5ed0" +checksum = "be0484ea4ef6bb9c575b4fdabde7e31340a8d2dbc7d52b321ac83da703249f95" dependencies = [ "cc", "tree-sitter-language", @@ -12976,9 +13162,9 @@ dependencies = [ [[package]] name = "tree-sitter-rust" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffbbcb780348fbae8395742ae5b34c1fd794e4085d43aac9f259387f9a84dc8" +checksum = "137ff3de3cc8a98302d048963459ead91135d4a1b423f09d25028b847ec3d3e3" dependencies = [ "cc", "tree-sitter-language", @@ -12986,9 +13172,9 @@ dependencies = [ [[package]] name = "tree-sitter-typescript" -version = "0.23.0" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecf1585ae2a9dddc2b1d4c0e2140b2ec9876e2a25fd79de47fcf7dae0384685" +checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff" dependencies = [ "cc", "tree-sitter-language", @@ -13017,19 +13203,38 @@ checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" [[package]] name = "tungstenite" -version = "0.20.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" dependencies = [ "byteorder", - "bytes 1.7.2", + "bytes 1.8.0", "data-encoding", "http 0.2.12", "httparse", "log", "rand 0.8.5", "sha1", - "thiserror", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes 1.8.0", + "data-encoding", + "http 0.2.12", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", "url", "utf-8", ] @@ -13041,14 +13246,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" dependencies = [ "byteorder", - "bytes 1.7.2", + "bytes 1.8.0", "data-encoding", "http 1.1.0", "httparse", "log", "rand 0.8.5", "sha1", - "thiserror", + "thiserror 1.0.69", "url", "utf-8", ] @@ -13060,14 +13265,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" dependencies = [ "byteorder", - "bytes 1.7.2", + "bytes 1.8.0", "data-encoding", "http 1.1.0", "httparse", "log", "rand 0.8.5", "sha1", - "thiserror", + "thiserror 1.0.69", "utf-8", ] @@ -13085,9 +13290,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uds_windows" @@ -13141,18 +13346,15 @@ dependencies = [ [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-bidi-mirroring" @@ -13168,9 +13370,9 @@ checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-linebreak" @@ -13180,18 +13382,18 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-script" @@ -13201,21 +13403,21 @@ checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unicode_categories" @@ -13237,9 +13439,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", "idna", @@ -13281,6 +13483,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -13295,7 +13509,7 @@ dependencies = [ "async-fs 1.6.0", "collections", "dirs 4.0.0", - "futures 0.3.30", + "futures 0.3.31", "futures-lite 1.13.0", "git2", "globset", @@ -13313,9 +13527,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom 0.2.15", "serde", @@ -13341,9 +13555,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" dependencies = [ "value-bag-serde1", "value-bag-sval2", @@ -13351,9 +13565,9 @@ dependencies = [ [[package]] name = "value-bag-serde1" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccacf50c5cb077a9abb723c5bcb5e0754c1a433f1e1de89edc328e2760b6328b" +checksum = "4bb773bd36fd59c7ca6e336c94454d9c66386416734817927ac93d81cb3c5b0b" dependencies = [ "erased-serde", "serde", @@ -13362,9 +13576,9 @@ dependencies = [ [[package]] name = "value-bag-sval2" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1785bae486022dfb9703915d42287dcb284c1ee37bd1080eeba78cc04721285b" +checksum = "53a916a702cac43a88694c97657d449775667bcd14b70419441d05b7fea4a83a" dependencies = [ "sval", "sval_buffer", @@ -13419,7 +13633,7 @@ dependencies = [ "command_palette", "command_palette_hooks", "editor", - "futures 0.3.30", + "futures 0.3.31", "gpui", "indoc", "itertools 0.13.0", @@ -13534,7 +13748,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" dependencies = [ - "bytes 1.7.2", + "bytes 1.8.0", "futures-channel", "futures-util", "headers", @@ -13576,9 +13790,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -13587,9 +13801,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", @@ -13602,9 +13816,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -13614,9 +13828,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -13624,9 +13838,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -13637,9 +13851,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-encoder" @@ -13666,7 +13880,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2" dependencies = [ "anyhow", - "indexmap 2.4.0", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -13677,9 +13891,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -13695,7 +13909,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" dependencies = [ "bitflags 2.6.0", - "indexmap 2.4.0", + "indexmap 2.6.0", "semver", ] @@ -13708,7 +13922,7 @@ dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", "hashbrown 0.14.5", - "indexmap 2.4.0", + "indexmap 2.6.0", "semver", "serde", ] @@ -13738,7 +13952,7 @@ dependencies = [ "cfg-if", "encoding_rs", "hashbrown 0.14.5", - "indexmap 2.4.0", + "indexmap 2.6.0", "libc", "libm", "log", @@ -13749,7 +13963,7 @@ dependencies = [ "paste", "postcard", "psm", - "rustix 0.38.35", + "rustix 0.38.40", "semver", "serde", "serde_derive", @@ -13781,9 +13995,9 @@ dependencies = [ [[package]] name = "wasmtime-c-api-impl" -version = "24.0.0" +version = "24.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765e302e7d9125e614aaeec3ad6b6083605393004eca00214106a4ff6b47fc58" +checksum = "4e038dd412700174019867608617127e7cc4f113f764dd10e7488dbf5f47b191" dependencies = [ "anyhow", "log", @@ -13795,9 +14009,9 @@ dependencies = [ [[package]] name = "wasmtime-c-api-macros" -version = "24.0.0" +version = "24.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d09d02eaa84aa2de5babee7b0296557ad6e4903bb10aa8d135e393e753a43d6" +checksum = "bde0ca2263811d980ab676bcb2a190c990737f58969a908976101ad208149a17" dependencies = [ "proc-macro2", "quote", @@ -13842,7 +14056,7 @@ dependencies = [ "log", "object", "target-lexicon", - "thiserror", + "thiserror 1.0.69", "wasmparser 0.215.0", "wasmtime-environ", "wasmtime-versioned-export-macros", @@ -13859,7 +14073,7 @@ dependencies = [ "cranelift-bitset", "cranelift-entity", "gimli 0.29.0", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "object", "postcard", @@ -13884,7 +14098,7 @@ dependencies = [ "anyhow", "cc", "cfg-if", - "rustix 0.38.35", + "rustix 0.38.40", "wasmtime-asm-macros", "wasmtime-versioned-export-macros", "windows-sys 0.52.0", @@ -13935,27 +14149,27 @@ dependencies = [ [[package]] name = "wasmtime-wasi" -version = "24.0.1" +version = "24.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda03f5bfd5c4cc09f75c7e44846663f25f2c48a2d688fbfb5c7a33af6cf34f5" +checksum = "f88f94e393084426f5055d57ce7ae6346ae623783ee6792f411282d6b9e1e5c3" dependencies = [ "anyhow", "async-trait", "bitflags 2.6.0", - "bytes 1.7.2", + "bytes 1.8.0", "cap-fs-ext", "cap-net-ext", "cap-rand", "cap-std", "cap-time-ext", "fs-set-times", - "futures 0.3.30", + "futures 0.3.31", "io-extras", "io-lifetimes 2.0.3", "once_cell", - "rustix 0.38.35", + "rustix 0.38.40", "system-interface", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "url", @@ -13989,7 +14203,7 @@ checksum = "c58b085b2d330e5057dddd31f3ca527569b90fcdd35f6d373420c304927a5190" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.4.0", + "indexmap 2.6.0", "wit-parser 0.215.0", ] @@ -14004,13 +14218,13 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90e11ce2ca99c97b940ee83edbae9da2d56a08f9ea8158550fd77fa31722993" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.35", + "rustix 0.38.40", "scoped-tls", "smallvec", "wayland-sys", @@ -14018,23 +14232,23 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e321577a0a165911bdcfb39cf029302479d7527b517ee58ab0f6ad09edf0943" +checksum = "b66249d3fc69f76fd74c82cc319300faa554e9d865dab1f7cd66cc20db10b280" dependencies = [ "bitflags 2.6.0", - "rustix 0.38.35", + "rustix 0.38.40", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-cursor" -version = "0.31.5" +version = "0.31.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef9489a8df197ebf3a8ce8a7a7f0a2320035c3743f3c1bd0bdbccf07ce64f95" +checksum = "32b08bc3aafdb0035e7fe0fdf17ba0c09c268732707dca4ae098f60cb28c9e4c" dependencies = [ - "rustix 0.38.35", + "rustix 0.38.40", "wayland-client", "xcursor", ] @@ -14066,20 +14280,20 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.4" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b56f89937f1cf2ee1f1259cf2936a17a1f45d8f0aa1019fae6d470d304cfa6" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", - "quick-xml 0.34.0", + "quick-xml 0.36.2", "quote", ] [[package]] name = "wayland-sys" -version = "0.31.4" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43676fe2daf68754ecf1d72026e4e6c15483198b5d24e888b74d3f22f887a148" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" dependencies = [ "dlib", "log", @@ -14089,9 +14303,19 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +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", @@ -14163,7 +14387,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.35", + "rustix 0.38.40", ] [[package]] @@ -14174,30 +14398,30 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", - "rustix 0.38.35", + "rustix 0.38.40", "winsafe", ] [[package]] name = "whoami" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.4.1", + "redox_syscall 0.5.7", "wasite", ] [[package]] name = "wiggle" -version = "24.0.1" +version = "24.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3b31bd2b4d2d82a4b747b8dbc45f566214214a4ffdc5690429a73bc221dc8a" +checksum = "c72a4c92952216582f55eab27819a1fe8d3c54b292b7b8e5f849b23bfed96e78" dependencies = [ "anyhow", "async-trait", "bitflags 2.6.0", - "thiserror", + "thiserror 1.0.69", "tracing", "wasmtime", "wiggle-macro", @@ -14205,9 +14429,9 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "24.0.1" +version = "24.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2c6136b195fc12067aa9d4e7a5baf118729394df7bc7cbf8c63119bc9f2a7cd" +checksum = "cb744fb938a9fc38207838829b4a43831c1de499e3526eaea71deeff4d9cbb83" dependencies = [ "anyhow", "heck 0.4.1", @@ -14220,9 +14444,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "24.0.1" +version = "24.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a41eaceee468da976ac43b85c4eb82e482f828d5e8e56f49f90dfac2d9bc3b4" +checksum = "7cef395fff17bf8f9c1dee6c0e12801a3ba24928139af0ecb5ccb82ff87bf9d2" dependencies = [ "proc-macro2", "quote", @@ -14288,6 +14512,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[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" @@ -14317,19 +14551,42 @@ dependencies = [ "windows-targets 0.52.6", ] +[[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", - "windows-interface", + "windows-implement 0.58.0", + "windows-interface 0.58.0", "windows-result 0.2.0", "windows-strings", "windows-targets 0.52.6", ] +[[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 2.0.87", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -14341,6 +14598,17 @@ dependencies = [ "syn 2.0.87", ] +[[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 2.0.87", +] + [[package]] name = "windows-interface" version = "0.58.0" @@ -14616,9 +14884,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -14713,7 +14981,7 @@ checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.4.0", + "indexmap 2.6.0", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -14741,7 +15009,7 @@ checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825" dependencies = [ "anyhow", "bitflags 2.6.0", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "serde", "serde_derive", @@ -14760,7 +15028,7 @@ checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" dependencies = [ "anyhow", "id-arena", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "semver", "serde", @@ -14778,7 +15046,7 @@ checksum = "935a97eaffd57c3b413aa510f8f0b550a4a9fe7d59e79cd8b89a83dcb860321f" dependencies = [ "anyhow", "id-arena", - "indexmap 2.4.0", + "indexmap 2.6.0", "log", "semver", "serde", @@ -14796,7 +15064,7 @@ checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" dependencies = [ "anyhow", "log", - "thiserror", + "thiserror 1.0.69", "wast", ] @@ -14816,7 +15084,7 @@ dependencies = [ "derive_more", "env_logger 0.11.5", "fs", - "futures 0.3.30", + "futures 0.3.31", "git", "gpui", "http_client", @@ -14853,7 +15121,7 @@ dependencies = [ "collections", "env_logger 0.11.5", "fs", - "futures 0.3.30", + "futures 0.3.31", "fuzzy", "git", "git2", @@ -14880,6 +15148,18 @@ dependencies = [ "util", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -14891,9 +15171,9 @@ dependencies = [ [[package]] name = "x11-clipboard" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98785a09322d7446e28a13203d2cae1059a0dd3dfb32cb06d0a225f023d8286" +checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" dependencies = [ "libc", "x11rb", @@ -14908,7 +15188,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "rustix 0.38.35", + "rustix 0.38.40", "x11rb-protocol", ] @@ -15056,6 +15336,30 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + [[package]] name = "zbus" version = "4.4.0" @@ -15065,9 +15369,9 @@ dependencies = [ "async-broadcast", "async-executor", "async-fs 2.1.2", - "async-io 2.3.4", + "async-io 2.4.0", "async-lock 3.4.0", - "async-process 2.2.4", + "async-process 2.3.0", "async-recursion 1.1.1", "async-task", "async-trait", @@ -15156,7 +15460,7 @@ dependencies = [ "file_finder", "file_icons", "fs", - "futures 0.3.30", + "futures 0.3.31", "git", "git_hosting_providers", "go_to_line", @@ -15451,6 +15755,27 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" @@ -15473,15 +15798,15 @@ dependencies = [ [[package]] name = "zeromq" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0560d00172817b7f7c2265060783519c475702ae290b154115ca75e976d4d0" +checksum = "6a4528179201f6eecf211961a7d3276faa61554c82651ecc66387f68fc3004bd" dependencies = [ "async-dispatcher", "async-std", "async-trait", "asynchronous-codec", - "bytes 1.7.2", + "bytes 1.8.0", "crossbeam-queue", "dashmap 5.5.3", "futures-channel", @@ -15494,10 +15819,32 @@ dependencies = [ "parking_lot", "rand 0.8.5", "regex", - "thiserror", + "thiserror 1.0.69", "uuid", ] +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/Cargo.toml b/Cargo.toml index ef5569f72b..98922a7ca2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -368,12 +368,14 @@ indexmap = { version = "1.6.2", features = ["serde"] } indoc = "2" itertools = "0.13.0" jsonwebtoken = "9.3" +jupyter-protocol = { version = "0.2.0" } +jupyter-websocket-client = { version = "0.4.1" } libc = "0.2" linkify = "0.10.0" log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] } markup5ever_rcdom = "0.3.0" nanoid = "0.4" -nbformat = "0.5.0" +nbformat = "0.6.0" nix = "0.29" num-format = "0.4.4" once_cell = "1.19.0" @@ -407,7 +409,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f "stream", ] } rsa = "0.9.6" -runtimelib = { version = "0.19.0", default-features = false, features = [ +runtimelib = { version = "0.21.0", default-features = false, features = [ "async-dispatcher-runtime", ] } rustc-demangle = "0.1.23" diff --git a/crates/quick_action_bar/src/repl_menu.rs b/crates/quick_action_bar/src/repl_menu.rs index d2649d4180..b9ae940579 100644 --- a/crates/quick_action_bar/src/repl_menu.rs +++ b/crates/quick_action_bar/src/repl_menu.rs @@ -402,7 +402,7 @@ fn session_state(session: View, cx: &WindowContext) -> ReplMenuState { status: session.kernel.status(), ..fill_fields() }, - Kernel::RunningKernel(kernel) => match &kernel.execution_state { + Kernel::RunningKernel(kernel) => match &kernel.execution_state() { ExecutionState::Idle => ReplMenuState { tooltip: format!("Run code on {} ({})", kernel_name, kernel_language).into(), indicator: Some(Indicator::dot().color(Color::Success)), diff --git a/crates/repl/Cargo.toml b/crates/repl/Cargo.toml index b170def71f..3f59ca325b 100644 --- a/crates/repl/Cargo.toml +++ b/crates/repl/Cargo.toml @@ -25,6 +25,8 @@ feature_flags.workspace = true futures.workspace = true gpui.workspace = true image.workspace = true +jupyter-websocket-client.workspace = true +jupyter-protocol.workspace = true language.workspace = true log.workspace = true markdown_preview.workspace = true diff --git a/crates/repl/src/kernels/mod.rs b/crates/repl/src/kernels/mod.rs new file mode 100644 index 0000000000..cea5adb59e --- /dev/null +++ b/crates/repl/src/kernels/mod.rs @@ -0,0 +1,227 @@ +mod native_kernel; +use std::{fmt::Debug, future::Future, path::PathBuf}; + +use futures::{ + channel::mpsc::{self, Receiver}, + future::Shared, + stream, +}; +use gpui::{AppContext, Model, Task}; +use language::LanguageName; +pub use native_kernel::*; + +mod remote_kernels; +use project::{Project, WorktreeId}; +pub use remote_kernels::*; + +use anyhow::Result; +use runtimelib::{ExecutionState, JupyterKernelspec, JupyterMessage, KernelInfoReply}; +use smol::process::Command; +use ui::SharedString; + +pub type JupyterMessageChannel = stream::SelectAll>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum KernelSpecification { + Remote(RemoteKernelSpecification), + Jupyter(LocalKernelSpecification), + PythonEnv(LocalKernelSpecification), +} + +impl KernelSpecification { + pub fn name(&self) -> SharedString { + match self { + Self::Jupyter(spec) => spec.name.clone().into(), + Self::PythonEnv(spec) => spec.name.clone().into(), + Self::Remote(spec) => spec.name.clone().into(), + } + } + + pub fn type_name(&self) -> SharedString { + match self { + Self::Jupyter(_) => "Jupyter".into(), + Self::PythonEnv(_) => "Python Environment".into(), + Self::Remote(_) => "Remote".into(), + } + } + + pub fn path(&self) -> SharedString { + SharedString::from(match self { + Self::Jupyter(spec) => spec.path.to_string_lossy().to_string(), + Self::PythonEnv(spec) => spec.path.to_string_lossy().to_string(), + Self::Remote(spec) => spec.url.to_string(), + }) + } + + pub fn language(&self) -> SharedString { + SharedString::from(match self { + Self::Jupyter(spec) => spec.kernelspec.language.clone(), + Self::PythonEnv(spec) => spec.kernelspec.language.clone(), + Self::Remote(spec) => spec.kernelspec.language.clone(), + }) + } +} + +pub fn python_env_kernel_specifications( + project: &Model, + worktree_id: WorktreeId, + cx: &mut AppContext, +) -> impl Future>> { + let python_language = LanguageName::new("Python"); + let toolchains = project + .read(cx) + .available_toolchains(worktree_id, python_language, cx); + let background_executor = cx.background_executor().clone(); + + async move { + let toolchains = if let Some(toolchains) = toolchains.await { + toolchains + } else { + return Ok(Vec::new()); + }; + + let kernelspecs = toolchains.toolchains.into_iter().map(|toolchain| { + background_executor.spawn(async move { + let python_path = toolchain.path.to_string(); + + // Check if ipykernel is installed + let ipykernel_check = Command::new(&python_path) + .args(&["-c", "import ipykernel"]) + .output() + .await; + + if ipykernel_check.is_ok() && ipykernel_check.unwrap().status.success() { + // Create a default kernelspec for this environment + let default_kernelspec = JupyterKernelspec { + argv: vec![ + python_path.clone(), + "-m".to_string(), + "ipykernel_launcher".to_string(), + "-f".to_string(), + "{connection_file}".to_string(), + ], + display_name: toolchain.name.to_string(), + language: "python".to_string(), + interrupt_mode: None, + metadata: None, + env: None, + }; + + Some(KernelSpecification::PythonEnv(LocalKernelSpecification { + name: toolchain.name.to_string(), + path: PathBuf::from(&python_path), + kernelspec: default_kernelspec, + })) + } else { + None + } + }) + }); + + let kernel_specs = futures::future::join_all(kernelspecs) + .await + .into_iter() + .flatten() + .collect(); + + anyhow::Ok(kernel_specs) + } +} + +pub trait RunningKernel: Send + Debug { + fn request_tx(&self) -> mpsc::Sender; + fn working_directory(&self) -> &PathBuf; + fn execution_state(&self) -> &ExecutionState; + fn set_execution_state(&mut self, state: ExecutionState); + fn kernel_info(&self) -> Option<&KernelInfoReply>; + fn set_kernel_info(&mut self, info: KernelInfoReply); + fn force_shutdown(&mut self) -> anyhow::Result<()>; +} + +#[derive(Debug, Clone)] +pub enum KernelStatus { + Idle, + Busy, + Starting, + Error, + ShuttingDown, + Shutdown, + Restarting, +} + +impl KernelStatus { + pub fn is_connected(&self) -> bool { + match self { + KernelStatus::Idle | KernelStatus::Busy => true, + _ => false, + } + } +} + +impl ToString for KernelStatus { + fn to_string(&self) -> String { + match self { + KernelStatus::Idle => "Idle".to_string(), + KernelStatus::Busy => "Busy".to_string(), + KernelStatus::Starting => "Starting".to_string(), + KernelStatus::Error => "Error".to_string(), + KernelStatus::ShuttingDown => "Shutting Down".to_string(), + KernelStatus::Shutdown => "Shutdown".to_string(), + KernelStatus::Restarting => "Restarting".to_string(), + } + } +} + +#[derive(Debug)] +pub enum Kernel { + RunningKernel(Box), + StartingKernel(Shared>), + ErroredLaunch(String), + ShuttingDown, + Shutdown, + Restarting, +} + +impl From<&Kernel> for KernelStatus { + fn from(kernel: &Kernel) -> Self { + match kernel { + Kernel::RunningKernel(kernel) => match kernel.execution_state() { + ExecutionState::Idle => KernelStatus::Idle, + ExecutionState::Busy => KernelStatus::Busy, + }, + Kernel::StartingKernel(_) => KernelStatus::Starting, + Kernel::ErroredLaunch(_) => KernelStatus::Error, + Kernel::ShuttingDown => KernelStatus::ShuttingDown, + Kernel::Shutdown => KernelStatus::Shutdown, + Kernel::Restarting => KernelStatus::Restarting, + } + } +} + +impl Kernel { + pub fn status(&self) -> KernelStatus { + self.into() + } + + pub fn set_execution_state(&mut self, status: &ExecutionState) { + if let Kernel::RunningKernel(running_kernel) = self { + running_kernel.set_execution_state(status.clone()); + } + } + + pub fn set_kernel_info(&mut self, kernel_info: &KernelInfoReply) { + if let Kernel::RunningKernel(running_kernel) = self { + running_kernel.set_kernel_info(kernel_info.clone()); + } + } + + pub fn is_shutting_down(&self) -> bool { + match self { + Kernel::Restarting | Kernel::ShuttingDown => true, + Kernel::RunningKernel(_) + | Kernel::StartingKernel(_) + | Kernel::ErroredLaunch(_) + | Kernel::Shutdown => false, + } + } +} diff --git a/crates/repl/src/kernels.rs b/crates/repl/src/kernels/native_kernel.rs similarity index 62% rename from crates/repl/src/kernels.rs rename to crates/repl/src/kernels/native_kernel.rs index 8ad8a05648..8a232c3de9 100644 --- a/crates/repl/src/kernels.rs +++ b/crates/repl/src/kernels/native_kernel.rs @@ -1,69 +1,24 @@ use anyhow::{Context as _, Result}; use futures::{ - channel::mpsc::{self, Receiver}, - future::Shared, - stream::{self, SelectAll, StreamExt}, + channel::mpsc::{self}, + stream::{SelectAll, StreamExt}, SinkExt as _, }; -use gpui::{AppContext, EntityId, Model, Task}; -use language::LanguageName; -use project::{Fs, Project, WorktreeId}; -use runtimelib::{ - dirs, ConnectionInfo, ExecutionState, JupyterKernelspec, JupyterMessage, JupyterMessageContent, - KernelInfoReply, -}; +use gpui::{AppContext, EntityId, Task}; +use jupyter_protocol::{JupyterMessage, JupyterMessageContent, KernelInfoReply}; +use project::Fs; +use runtimelib::{dirs, ConnectionInfo, ExecutionState, JupyterKernelspec}; use smol::{net::TcpListener, process::Command}; use std::{ env, fmt::Debug, - future::Future, net::{IpAddr, Ipv4Addr, SocketAddr}, path::PathBuf, sync::Arc, }; -use ui::SharedString; use uuid::Uuid; -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum KernelSpecification { - Remote(RemoteKernelSpecification), - Jupyter(LocalKernelSpecification), - PythonEnv(LocalKernelSpecification), -} - -impl KernelSpecification { - pub fn name(&self) -> SharedString { - match self { - Self::Jupyter(spec) => spec.name.clone().into(), - Self::PythonEnv(spec) => spec.name.clone().into(), - Self::Remote(spec) => spec.name.clone().into(), - } - } - - pub fn type_name(&self) -> SharedString { - match self { - Self::Jupyter(_) => "Jupyter".into(), - Self::PythonEnv(_) => "Python Environment".into(), - Self::Remote(_) => "Remote".into(), - } - } - - pub fn path(&self) -> SharedString { - SharedString::from(match self { - Self::Jupyter(spec) => spec.path.to_string_lossy().to_string(), - Self::PythonEnv(spec) => spec.path.to_string_lossy().to_string(), - Self::Remote(spec) => spec.url.to_string(), - }) - } - - pub fn language(&self) -> SharedString { - SharedString::from(match self { - Self::Jupyter(spec) => spec.kernelspec.language.clone(), - Self::PythonEnv(spec) => spec.kernelspec.language.clone(), - Self::Remote(spec) => spec.kernelspec.language.clone(), - }) - } -} +use super::{JupyterMessageChannel, RunningKernel}; #[derive(Debug, Clone)] pub struct LocalKernelSpecification { @@ -80,22 +35,6 @@ impl PartialEq for LocalKernelSpecification { impl Eq for LocalKernelSpecification {} -#[derive(Debug, Clone)] -pub struct RemoteKernelSpecification { - pub name: String, - pub url: String, - pub token: String, - pub kernelspec: JupyterKernelspec, -} - -impl PartialEq for RemoteKernelSpecification { - fn eq(&self, other: &Self) -> bool { - self.name == other.name && self.url == other.url - } -} - -impl Eq for RemoteKernelSpecification {} - impl LocalKernelSpecification { #[must_use] fn command(&self, connection_path: &PathBuf) -> Result { @@ -147,95 +86,7 @@ async fn peek_ports(ip: IpAddr) -> Result<[u16; 5]> { Ok(ports) } -#[derive(Debug, Clone)] -pub enum KernelStatus { - Idle, - Busy, - Starting, - Error, - ShuttingDown, - Shutdown, - Restarting, -} - -impl KernelStatus { - pub fn is_connected(&self) -> bool { - match self { - KernelStatus::Idle | KernelStatus::Busy => true, - _ => false, - } - } -} - -impl ToString for KernelStatus { - fn to_string(&self) -> String { - match self { - KernelStatus::Idle => "Idle".to_string(), - KernelStatus::Busy => "Busy".to_string(), - KernelStatus::Starting => "Starting".to_string(), - KernelStatus::Error => "Error".to_string(), - KernelStatus::ShuttingDown => "Shutting Down".to_string(), - KernelStatus::Shutdown => "Shutdown".to_string(), - KernelStatus::Restarting => "Restarting".to_string(), - } - } -} - -impl From<&Kernel> for KernelStatus { - fn from(kernel: &Kernel) -> Self { - match kernel { - Kernel::RunningKernel(kernel) => match kernel.execution_state { - ExecutionState::Idle => KernelStatus::Idle, - ExecutionState::Busy => KernelStatus::Busy, - }, - Kernel::StartingKernel(_) => KernelStatus::Starting, - Kernel::ErroredLaunch(_) => KernelStatus::Error, - Kernel::ShuttingDown => KernelStatus::ShuttingDown, - Kernel::Shutdown => KernelStatus::Shutdown, - Kernel::Restarting => KernelStatus::Restarting, - } - } -} - -#[derive(Debug)] -pub enum Kernel { - RunningKernel(RunningKernel), - StartingKernel(Shared>), - ErroredLaunch(String), - ShuttingDown, - Shutdown, - Restarting, -} - -impl Kernel { - pub fn status(&self) -> KernelStatus { - self.into() - } - - pub fn set_execution_state(&mut self, status: &ExecutionState) { - if let Kernel::RunningKernel(running_kernel) = self { - running_kernel.execution_state = status.clone(); - } - } - - pub fn set_kernel_info(&mut self, kernel_info: &KernelInfoReply) { - if let Kernel::RunningKernel(running_kernel) = self { - running_kernel.kernel_info = Some(kernel_info.clone()); - } - } - - pub fn is_shutting_down(&self) -> bool { - match self { - Kernel::Restarting | Kernel::ShuttingDown => true, - Kernel::RunningKernel(_) - | Kernel::StartingKernel(_) - | Kernel::ErroredLaunch(_) - | Kernel::Shutdown => false, - } - } -} - -pub struct RunningKernel { +pub struct NativeRunningKernel { pub process: smol::process::Child, _shell_task: Task>, _iopub_task: Task>, @@ -248,9 +99,7 @@ pub struct RunningKernel { pub kernel_info: Option, } -type JupyterMessageChannel = stream::SelectAll>; - -impl Debug for RunningKernel { +impl Debug for NativeRunningKernel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("RunningKernel") .field("process", &self.process) @@ -258,25 +107,14 @@ impl Debug for RunningKernel { } } -impl RunningKernel { +impl NativeRunningKernel { pub fn new( - kernel_specification: KernelSpecification, + kernel_specification: LocalKernelSpecification, entity_id: EntityId, working_directory: PathBuf, fs: Arc, cx: &mut AppContext, ) -> Task> { - let kernel_specification = match kernel_specification { - KernelSpecification::Jupyter(spec) => spec, - KernelSpecification::PythonEnv(spec) => spec, - KernelSpecification::Remote(_spec) => { - // todo!(): Implement remote kernel specification - return Task::ready(Err(anyhow::anyhow!( - "Running remote kernels is not supported" - ))); - } - }; - cx.spawn(|cx| async move { let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); let ports = peek_ports(ip).await?; @@ -315,15 +153,13 @@ impl RunningKernel { let session_id = Uuid::new_v4().to_string(); - let mut iopub_socket = connection_info - .create_client_iopub_connection("", &session_id) - .await?; - let mut shell_socket = connection_info - .create_client_shell_connection(&session_id) - .await?; - let mut control_socket = connection_info - .create_client_control_connection(&session_id) - .await?; + let mut iopub_socket = + runtimelib::create_client_iopub_connection(&connection_info, "", &session_id) + .await?; + let mut shell_socket = + runtimelib::create_client_shell_connection(&connection_info, &session_id).await?; + let mut control_socket = + runtimelib::create_client_control_connection(&connection_info, &session_id).await?; let (mut iopub, iosub) = futures::channel::mpsc::channel(100); @@ -410,7 +246,43 @@ impl RunningKernel { } } -impl Drop for RunningKernel { +impl RunningKernel for NativeRunningKernel { + fn request_tx(&self) -> mpsc::Sender { + self.request_tx.clone() + } + + fn working_directory(&self) -> &PathBuf { + &self.working_directory + } + + fn execution_state(&self) -> &ExecutionState { + &self.execution_state + } + + fn set_execution_state(&mut self, state: ExecutionState) { + self.execution_state = state; + } + + fn kernel_info(&self) -> Option<&KernelInfoReply> { + self.kernel_info.as_ref() + } + + fn set_kernel_info(&mut self, info: KernelInfoReply) { + self.kernel_info = Some(info); + } + + fn force_shutdown(&mut self) -> anyhow::Result<()> { + match self.process.kill() { + Ok(_) => Ok(()), + Err(error) => Err(anyhow::anyhow!( + "Failed to kill the kernel process: {}", + error + )), + } + } +} + +impl Drop for NativeRunningKernel { fn drop(&mut self) { std::fs::remove_file(&self.connection_path).ok(); self.request_tx.close_channel(); @@ -467,72 +339,6 @@ async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result, - worktree_id: WorktreeId, - cx: &mut AppContext, -) -> impl Future>> { - let python_language = LanguageName::new("Python"); - let toolchains = project - .read(cx) - .available_toolchains(worktree_id, python_language, cx); - let background_executor = cx.background_executor().clone(); - - async move { - let toolchains = if let Some(toolchains) = toolchains.await { - toolchains - } else { - return Ok(Vec::new()); - }; - - let kernelspecs = toolchains.toolchains.into_iter().map(|toolchain| { - background_executor.spawn(async move { - let python_path = toolchain.path.to_string(); - - // Check if ipykernel is installed - let ipykernel_check = Command::new(&python_path) - .args(&["-c", "import ipykernel"]) - .output() - .await; - - if ipykernel_check.is_ok() && ipykernel_check.unwrap().status.success() { - // Create a default kernelspec for this environment - let default_kernelspec = JupyterKernelspec { - argv: vec![ - python_path.clone(), - "-m".to_string(), - "ipykernel_launcher".to_string(), - "-f".to_string(), - "{connection_file}".to_string(), - ], - display_name: toolchain.name.to_string(), - language: "python".to_string(), - interrupt_mode: None, - metadata: None, - env: None, - }; - - Some(KernelSpecification::PythonEnv(LocalKernelSpecification { - name: toolchain.name.to_string(), - path: PathBuf::from(&python_path), - kernelspec: default_kernelspec, - })) - } else { - None - } - }) - }); - - let kernel_specs = futures::future::join_all(kernelspecs) - .await - .into_iter() - .flatten() - .collect(); - - anyhow::Ok(kernel_specs) - } -} - pub async fn local_kernel_specifications(fs: Arc) -> Result> { let mut data_dirs = dirs::data_dirs(); diff --git a/crates/repl/src/kernels/remote_kernels.rs b/crates/repl/src/kernels/remote_kernels.rs new file mode 100644 index 0000000000..9d2d5f2810 --- /dev/null +++ b/crates/repl/src/kernels/remote_kernels.rs @@ -0,0 +1,122 @@ +use futures::{channel::mpsc, StreamExt as _}; +use gpui::AppContext; +use jupyter_protocol::{ExecutionState, JupyterMessage, KernelInfoReply}; +// todo(kyle): figure out if this needs to be different +use runtimelib::JupyterKernelspec; + +use super::RunningKernel; +use jupyter_websocket_client::RemoteServer; +use std::fmt::Debug; + +#[derive(Debug, Clone)] +pub struct RemoteKernelSpecification { + pub name: String, + pub url: String, + pub token: String, + pub kernelspec: JupyterKernelspec, +} + +impl PartialEq for RemoteKernelSpecification { + fn eq(&self, other: &Self) -> bool { + self.name == other.name && self.url == other.url + } +} + +impl Eq for RemoteKernelSpecification {} + +pub struct RemoteRunningKernel { + remote_server: RemoteServer, + pub working_directory: std::path::PathBuf, + pub request_tx: mpsc::Sender, + pub execution_state: ExecutionState, + pub kernel_info: Option, +} + +impl RemoteRunningKernel { + pub async fn new( + kernelspec: RemoteKernelSpecification, + working_directory: std::path::PathBuf, + request_tx: mpsc::Sender, + _cx: &mut AppContext, + ) -> anyhow::Result<( + Self, + (), // Stream + )> { + let remote_server = RemoteServer { + base_url: kernelspec.url, + token: kernelspec.token, + }; + + // todo: launch a kernel to get a kernel ID + let kernel_id = "not-implemented"; + + let kernel_socket = remote_server.connect_to_kernel(kernel_id).await?; + + let (mut _w, mut _r) = kernel_socket.split(); + + let (_messages_tx, _messages_rx) = mpsc::channel::(100); + + // let routing_task = cx.background_executor().spawn({ + // async move { + // while let Some(message) = request_rx.next().await { + // w.send(message).await; + // } + // } + // }); + // let messages_rx = r.into(); + + anyhow::Ok(( + Self { + remote_server, + working_directory, + request_tx, + execution_state: ExecutionState::Idle, + kernel_info: None, + }, + (), + )) + } +} + +impl Debug for RemoteRunningKernel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RemoteRunningKernel") + // custom debug that keeps tokens out of logs + .field("remote_server url", &self.remote_server.base_url) + .field("working_directory", &self.working_directory) + .field("request_tx", &self.request_tx) + .field("execution_state", &self.execution_state) + .field("kernel_info", &self.kernel_info) + .finish() + } +} + +impl RunningKernel for RemoteRunningKernel { + fn request_tx(&self) -> futures::channel::mpsc::Sender { + self.request_tx.clone() + } + + fn working_directory(&self) -> &std::path::PathBuf { + &self.working_directory + } + + fn execution_state(&self) -> &runtimelib::ExecutionState { + &self.execution_state + } + + fn set_execution_state(&mut self, state: runtimelib::ExecutionState) { + self.execution_state = state; + } + + fn kernel_info(&self) -> Option<&runtimelib::KernelInfoReply> { + self.kernel_info.as_ref() + } + + fn set_kernel_info(&mut self, info: runtimelib::KernelInfoReply) { + self.kernel_info = Some(info); + } + + fn force_shutdown(&mut self) -> anyhow::Result<()> { + unimplemented!("force_shutdown") + } +} diff --git a/crates/repl/src/repl.rs b/crates/repl/src/repl.rs index be187ff16f..4d11734e29 100644 --- a/crates/repl/src/repl.rs +++ b/crates/repl/src/repl.rs @@ -1,6 +1,6 @@ pub mod components; mod jupyter_settings; -mod kernels; +pub mod kernels; pub mod notebook; mod outputs; mod repl_editor; diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index 74ce497572..513e85719d 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -1,7 +1,7 @@ use crate::components::KernelListItem; use crate::setup_editor_session_actions; use crate::{ - kernels::{Kernel, KernelSpecification, RunningKernel}, + kernels::{Kernel, KernelSpecification, NativeRunningKernel}, outputs::{ExecutionStatus, ExecutionView}, KernelStatus, }; @@ -246,13 +246,19 @@ impl Session { cx.entity_id().to_string(), ); - let kernel = RunningKernel::new( - self.kernel_specification.clone(), - entity_id, - working_directory, - self.fs.clone(), - cx, - ); + let kernel = match self.kernel_specification.clone() { + KernelSpecification::Jupyter(kernel_specification) + | KernelSpecification::PythonEnv(kernel_specification) => NativeRunningKernel::new( + kernel_specification, + entity_id, + working_directory, + self.fs.clone(), + cx, + ), + KernelSpecification::Remote(_remote_kernel_specification) => { + unimplemented!() + } + }; let pending_kernel = cx .spawn(|this, mut cx| async move { @@ -291,7 +297,7 @@ impl Session { .detach(); let status = kernel.process.status(); - session.kernel(Kernel::RunningKernel(kernel), cx); + session.kernel(Kernel::RunningKernel(Box::new(kernel)), cx); let process_status_task = cx.spawn(|session, mut cx| async move { let error_message = match status.await { @@ -416,7 +422,7 @@ impl Session { fn send(&mut self, message: JupyterMessage, _cx: &mut ViewContext) -> anyhow::Result<()> { if let Kernel::RunningKernel(kernel) = &mut self.kernel { - kernel.request_tx.try_send(message).ok(); + kernel.request_tx().try_send(message).ok(); } anyhow::Ok(()) @@ -631,7 +637,7 @@ impl Session { match kernel { Kernel::RunningKernel(mut kernel) => { - let mut request_tx = kernel.request_tx.clone(); + let mut request_tx = kernel.request_tx().clone(); cx.spawn(|this, mut cx| async move { let message: JupyterMessage = ShutdownRequest { restart: false }.into(); @@ -646,7 +652,7 @@ impl Session { }) .ok(); - kernel.process.kill().ok(); + kernel.force_shutdown().ok(); this.update(&mut cx, |session, cx| { session.clear_outputs(cx); @@ -674,7 +680,7 @@ impl Session { // Do nothing if already restarting } Kernel::RunningKernel(mut kernel) => { - let mut request_tx = kernel.request_tx.clone(); + let mut request_tx = kernel.request_tx().clone(); cx.spawn(|this, mut cx| async move { // Send shutdown request with restart flag @@ -692,7 +698,7 @@ impl Session { cx.background_executor().timer(Duration::from_secs(1)).await; // Force kill the kernel if it hasn't shut down - kernel.process.kill().ok(); + kernel.force_shutdown().ok(); // Start a new kernel this.update(&mut cx, |session, cx| { @@ -727,7 +733,7 @@ impl Render for Session { let (status_text, interrupt_button) = match &self.kernel { Kernel::RunningKernel(kernel) => ( kernel - .kernel_info + .kernel_info() .as_ref() .map(|info| info.language_info.name.clone()), Some( @@ -747,7 +753,7 @@ impl Render for Session { KernelListItem::new(self.kernel_specification.clone()) .status_color(match &self.kernel { - Kernel::RunningKernel(kernel) => match kernel.execution_state { + Kernel::RunningKernel(kernel) => match kernel.execution_state() { ExecutionState::Idle => Color::Success, ExecutionState::Busy => Color::Modified, }, From 0e26d22fead5a1a366302145d14c810d8804b8a1 Mon Sep 17 00:00:00 2001 From: uncenter <47499684+uncenter@users.noreply.github.com> Date: Mon, 18 Nov 2024 22:19:24 -0500 Subject: [PATCH 046/157] Add HTML injections for markdown (#20527) Closes https://github.com/zed-industries/extensions/issues/1588. | Before | After | | --- | --- | | ![CleanShot 2024-11-11 at 22 48 43](https://github.com/user-attachments/assets/9470e6a8-6a37-4b8f-8daa-5c8c5ed2bb17) | ![CleanShot 2024-11-11 at 22 49 43](https://github.com/user-attachments/assets/f2b858d0-9274-4332-b30e-61c13ac347c6) | Release Notes: - Added HTML injections for markdown syntax highlighting --- crates/languages/src/markdown/injections.scm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/languages/src/markdown/injections.scm b/crates/languages/src/markdown/injections.scm index 4b2493d4ce..5972a43eb1 100644 --- a/crates/languages/src/markdown/injections.scm +++ b/crates/languages/src/markdown/injections.scm @@ -5,3 +5,6 @@ ((inline) @content (#set! "language" "markdown-inline")) + +((html_block) @content + (#set! "language" "html")) From c0d11be75f0a2febec1edc86c70a2594eca79b44 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 19 Nov 2024 00:24:37 -0500 Subject: [PATCH 047/157] remove usages of `theme::color_alpha` --- crates/assistant/src/assistant_panel.rs | 4 ++-- crates/extensions_ui/src/components/extension_card.rs | 5 +---- crates/outline/src/outline.rs | 4 ++-- crates/theme/src/theme.rs | 8 -------- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index b682bfdcca..6e0ff77ef2 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2625,8 +2625,8 @@ impl ContextEditor { .px_1() .mr_0p5() .border_1() - .border_color(theme::color_alpha(colors.border_variant, 0.6)) - .bg(theme::color_alpha(colors.element_background, 0.6)) + .border_color(colors.border_variant.opacity(0.6)) + .bg(colors.element_background.opacity(0.6)) .child("esc"), ) .child("to cancel") diff --git a/crates/extensions_ui/src/components/extension_card.rs b/crates/extensions_ui/src/components/extension_card.rs index 2dc472f801..c44ac2063a 100644 --- a/crates/extensions_ui/src/components/extension_card.rs +++ b/crates/extensions_ui/src/components/extension_card.rs @@ -52,10 +52,7 @@ impl RenderOnce for ExtensionCard { .size_full() .items_center() .justify_center() - .bg(theme::color_alpha( - cx.theme().colors().elevated_surface_background, - 0.8, - )) + .bg(cx.theme().colors().elevated_surface_background.opacity(0.8)) .child(Label::new("Overridden by dev extension.")), ) }), diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 154b9297a3..e6ba24f75c 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -17,7 +17,7 @@ use language::{Outline, OutlineItem}; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate}; use settings::Settings; -use theme::{color_alpha, ActiveTheme, ThemeSettings}; +use theme::{ActiveTheme, ThemeSettings}; use ui::{prelude::*, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{DismissDecision, ModalView}; @@ -297,7 +297,7 @@ pub fn render_item( cx: &AppContext, ) -> StyledText { let highlight_style = HighlightStyle { - background_color: Some(color_alpha(cx.theme().colors().text_accent, 0.3)), + background_color: Some(cx.theme().colors().text_accent.opacity(0.3)), ..Default::default() }; let custom_highlights = match_ranges diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index cf860ad452..d501d3c118 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -333,14 +333,6 @@ impl Theme { } } -/// Compounds a color with an alpha value. -/// TODO: Replace this with a method on Hsla. -pub fn color_alpha(color: Hsla, alpha: f32) -> Hsla { - let mut color = color; - color.a = alpha; - color -} - /// Asynchronously reads the user theme from the specified path. pub async fn read_user_theme(theme_path: &Path, fs: Arc) -> Result { let reader = fs.open_sync(theme_path).await?; From a35b73e63e2bc7d2904b2d346adc27c15cf661a0 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Tue, 19 Nov 2024 00:24:48 -0500 Subject: [PATCH 048/157] Revert "remove usages of `theme::color_alpha`" This reverts commit c0d11be75f0a2febec1edc86c70a2594eca79b44. --- crates/assistant/src/assistant_panel.rs | 4 ++-- crates/extensions_ui/src/components/extension_card.rs | 5 ++++- crates/outline/src/outline.rs | 4 ++-- crates/theme/src/theme.rs | 8 ++++++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 6e0ff77ef2..b682bfdcca 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2625,8 +2625,8 @@ impl ContextEditor { .px_1() .mr_0p5() .border_1() - .border_color(colors.border_variant.opacity(0.6)) - .bg(colors.element_background.opacity(0.6)) + .border_color(theme::color_alpha(colors.border_variant, 0.6)) + .bg(theme::color_alpha(colors.element_background, 0.6)) .child("esc"), ) .child("to cancel") diff --git a/crates/extensions_ui/src/components/extension_card.rs b/crates/extensions_ui/src/components/extension_card.rs index c44ac2063a..2dc472f801 100644 --- a/crates/extensions_ui/src/components/extension_card.rs +++ b/crates/extensions_ui/src/components/extension_card.rs @@ -52,7 +52,10 @@ impl RenderOnce for ExtensionCard { .size_full() .items_center() .justify_center() - .bg(cx.theme().colors().elevated_surface_background.opacity(0.8)) + .bg(theme::color_alpha( + cx.theme().colors().elevated_surface_background, + 0.8, + )) .child(Label::new("Overridden by dev extension.")), ) }), diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index e6ba24f75c..154b9297a3 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -17,7 +17,7 @@ use language::{Outline, OutlineItem}; use ordered_float::OrderedFloat; use picker::{Picker, PickerDelegate}; use settings::Settings; -use theme::{ActiveTheme, ThemeSettings}; +use theme::{color_alpha, ActiveTheme, ThemeSettings}; use ui::{prelude::*, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{DismissDecision, ModalView}; @@ -297,7 +297,7 @@ pub fn render_item( cx: &AppContext, ) -> StyledText { let highlight_style = HighlightStyle { - background_color: Some(cx.theme().colors().text_accent.opacity(0.3)), + background_color: Some(color_alpha(cx.theme().colors().text_accent, 0.3)), ..Default::default() }; let custom_highlights = match_ranges diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index d501d3c118..cf860ad452 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -333,6 +333,14 @@ impl Theme { } } +/// Compounds a color with an alpha value. +/// TODO: Replace this with a method on Hsla. +pub fn color_alpha(color: Hsla, alpha: f32) -> Hsla { + let mut color = color; + color.a = alpha; + color +} + /// Asynchronously reads the user theme from the specified path. pub async fn read_user_theme(theme_path: &Path, fs: Arc) -> Result { let reader = fs.open_sync(theme_path).await?; From aae39071efcf65fd51feb07ab8149b85bfde230b Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Tue, 19 Nov 2024 09:41:44 +0100 Subject: [PATCH 049/157] editor: Show hints for using AI features on empty lines (#20824) Co-Authored-by: Thorsten Co-Authored-by: Antonio Screenshot: ![screenshot-2024-11-18-17 11 08@2x](https://github.com/user-attachments/assets/610fd7db-7476-4b9b-9465-a3d55df12340) TODO: - [x] docs Release Notes: - Added inline hints that guide users on how to invoke the inline assistant and open the assistant panel. (These hints can be disabled by setting `{"assistant": {"show_hints": false}}`.) --------- Co-authored-by: Thorsten Co-authored-by: Antonio Co-authored-by: Thorsten Ball --- assets/settings/default.json | 3 + crates/assistant/src/assistant_settings.rs | 11 ++ crates/editor/src/editor.rs | 36 ++++++ crates/editor/src/element.rs | 114 +++++++++-------- crates/gpui/src/window.rs | 22 +++- crates/outline_panel/src/outline_panel.rs | 6 +- crates/recent_projects/src/recent_projects.rs | 8 +- crates/zed/src/main.rs | 3 +- crates/zed/src/zed.rs | 1 + crates/zed/src/zed/assistant_hints.rs | 115 ++++++++++++++++++ docs/src/assistant/configuration.md | 26 ++-- docs/src/configuring-zed.md | 21 ++-- 12 files changed, 283 insertions(+), 83 deletions(-) create mode 100644 crates/zed/src/zed/assistant_hints.rs diff --git a/assets/settings/default.json b/assets/settings/default.json index 7c4a9a8111..3757dfe119 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -490,6 +490,9 @@ "version": "2", // Whether the assistant is enabled. "enabled": true, + // Whether to show inline hints showing the keybindings to use the inline assistant and the + // assistant panel. + "show_hints": true, // Whether to show the assistant panel button in the status bar. "button": true, // Where to dock the assistant panel. Can be 'left', 'right' or 'bottom'. diff --git a/crates/assistant/src/assistant_settings.rs b/crates/assistant/src/assistant_settings.rs index 5bfd406658..98188305fb 100644 --- a/crates/assistant/src/assistant_settings.rs +++ b/crates/assistant/src/assistant_settings.rs @@ -60,6 +60,7 @@ pub struct AssistantSettings { pub inline_alternatives: Vec, pub using_outdated_settings_version: bool, pub enable_experimental_live_diffs: bool, + pub show_hints: bool, } impl AssistantSettings { @@ -202,6 +203,7 @@ impl AssistantSettingsContent { AssistantSettingsContent::Versioned(settings) => match settings { VersionedAssistantSettingsContent::V1(settings) => AssistantSettingsContentV2 { enabled: settings.enabled, + show_hints: None, button: settings.button, dock: settings.dock, default_width: settings.default_width, @@ -242,6 +244,7 @@ impl AssistantSettingsContent { }, AssistantSettingsContent::Legacy(settings) => AssistantSettingsContentV2 { enabled: None, + show_hints: None, button: settings.button, dock: settings.dock, default_width: settings.default_width, @@ -354,6 +357,7 @@ impl Default for VersionedAssistantSettingsContent { fn default() -> Self { Self::V2(AssistantSettingsContentV2 { enabled: None, + show_hints: None, button: None, dock: None, default_width: None, @@ -371,6 +375,11 @@ pub struct AssistantSettingsContentV2 { /// /// Default: true enabled: Option, + /// Whether to show inline hints that show keybindings for inline assistant + /// and assistant panel. + /// + /// Default: true + show_hints: Option, /// Whether to show the assistant panel button in the status bar. /// /// Default: true @@ -505,6 +514,7 @@ impl Settings for AssistantSettings { let value = value.upgrade(); merge(&mut settings.enabled, value.enabled); + merge(&mut settings.show_hints, value.show_hints); merge(&mut settings.button, value.button); merge(&mut settings.dock, value.dock); merge( @@ -575,6 +585,7 @@ mod tests { }), inline_alternatives: None, enabled: None, + show_hints: None, button: None, dock: None, default_width: None, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 11d47daa6b..6167c24bff 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -540,6 +540,15 @@ pub enum IsVimMode { No, } +pub trait ActiveLineTrailerProvider { + fn render_active_line_trailer( + &mut self, + style: &EditorStyle, + focus_handle: &FocusHandle, + cx: &mut WindowContext, + ) -> Option; +} + /// Zed's primary text input `View`, allowing users to edit a [`MultiBuffer`] /// /// See the [module level documentation](self) for more information. @@ -667,6 +676,7 @@ pub struct Editor { next_scroll_position: NextScrollCursorCenterTopBottom, addons: HashMap>, _scroll_cursor_center_top_bottom_task: Task<()>, + active_line_trailer_provider: Option>, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] @@ -2200,6 +2210,7 @@ impl Editor { addons: HashMap::default(), _scroll_cursor_center_top_bottom_task: Task::ready(()), text_style_refinement: None, + active_line_trailer_provider: None, }; this.tasks_update_task = Some(this.refresh_runnables(cx)); this._subscriptions.extend(project_subscriptions); @@ -2488,6 +2499,16 @@ impl Editor { self.refresh_inline_completion(false, false, cx); } + pub fn set_active_line_trailer_provider( + &mut self, + provider: Option, + _cx: &mut ViewContext, + ) where + T: ActiveLineTrailerProvider + 'static, + { + self.active_line_trailer_provider = provider.map(|provider| Box::new(provider) as Box<_>); + } + pub fn placeholder_text(&self, _cx: &WindowContext) -> Option<&str> { self.placeholder_text.as_deref() } @@ -11844,6 +11865,21 @@ impl Editor { && self.has_blame_entries(cx) } + pub fn render_active_line_trailer( + &mut self, + style: &EditorStyle, + cx: &mut WindowContext, + ) -> Option { + if !self.newest_selection_head_on_empty_line(cx) || self.has_active_inline_completion(cx) { + return None; + } + + let focus_handle = self.focus_handle.clone(); + self.active_line_trailer_provider + .as_mut()? + .render_active_line_trailer(style, &focus_handle, cx) + } + fn has_blame_entries(&self, cx: &mut WindowContext) -> bool { self.blame() .map_or(false, |blame| blame.read(cx).has_generated_entries()) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7702134409..6e4538ae6d 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1412,7 +1412,7 @@ impl EditorElement { } #[allow(clippy::too_many_arguments)] - fn layout_inline_blame( + fn layout_active_line_trailer( &self, display_row: DisplayRow, display_snapshot: &DisplaySnapshot, @@ -1424,61 +1424,71 @@ impl EditorElement { line_height: Pixels, cx: &mut WindowContext, ) -> Option { - if !self + let render_inline_blame = self .editor - .update(cx, |editor, cx| editor.render_git_blame_inline(cx)) - { - return None; - } + .update(cx, |editor, cx| editor.render_git_blame_inline(cx)); + if render_inline_blame { + let workspace = self + .editor + .read(cx) + .workspace + .as_ref() + .map(|(w, _)| w.clone()); - let workspace = self - .editor - .read(cx) - .workspace - .as_ref() - .map(|(w, _)| w.clone()); + let display_point = DisplayPoint::new(display_row, 0); + let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row); - let display_point = DisplayPoint::new(display_row, 0); - let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row); + let blame = self.editor.read(cx).blame.clone()?; + let blame_entry = blame + .update(cx, |blame, cx| { + blame.blame_for_rows([Some(buffer_row)], cx).next() + }) + .flatten()?; - let blame = self.editor.read(cx).blame.clone()?; - let blame_entry = blame - .update(cx, |blame, cx| { - blame.blame_for_rows([Some(buffer_row)], cx).next() - }) - .flatten()?; + let mut element = + render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx); - let mut element = - render_inline_blame_entry(&blame, blame_entry, &self.style, workspace, cx); + let start_y = content_origin.y + + line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height); - let start_y = content_origin.y - + line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height); + let start_x = { + const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.; - let start_x = { - const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.; + let line_end = if let Some(crease_trailer) = crease_trailer { + crease_trailer.bounds.right() + } else { + content_origin.x - scroll_pixel_position.x + line_layout.width + }; + let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS; - let line_end = if let Some(crease_trailer) = crease_trailer { - crease_trailer.bounds.right() - } else { - content_origin.x - scroll_pixel_position.x + line_layout.width + let min_column_in_pixels = ProjectSettings::get_global(cx) + .git + .inline_blame + .and_then(|settings| settings.min_column) + .map(|col| self.column_pixels(col as usize, cx)) + .unwrap_or(px(0.)); + let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels; + + cmp::max(padded_line_end, min_start) }; - let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS; - let min_column_in_pixels = ProjectSettings::get_global(cx) - .git - .inline_blame - .and_then(|settings| settings.min_column) - .map(|col| self.column_pixels(col as usize, cx)) - .unwrap_or(px(0.)); - let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels; + let absolute_offset = point(start_x, start_y); + element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), cx); - cmp::max(padded_line_end, min_start) - }; + Some(element) + } else if let Some(mut element) = self.editor.update(cx, |editor, cx| { + editor.render_active_line_trailer(&self.style, cx) + }) { + let start_y = content_origin.y + + line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height); + let start_x = content_origin.x - scroll_pixel_position.x + em_width; + let absolute_offset = point(start_x, start_y); + element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), cx); - let absolute_offset = point(start_x, start_y); - element.prepaint_as_root(absolute_offset, AvailableSpace::min_size(), cx); - - Some(element) + Some(element) + } else { + None + } } #[allow(clippy::too_many_arguments)] @@ -3454,7 +3464,7 @@ impl EditorElement { self.paint_lines(&invisible_display_ranges, layout, cx); self.paint_redactions(layout, cx); self.paint_cursors(layout, cx); - self.paint_inline_blame(layout, cx); + self.paint_active_line_trailer(layout, cx); cx.with_element_namespace("crease_trailers", |cx| { for trailer in layout.crease_trailers.iter_mut().flatten() { trailer.element.paint(cx); @@ -3936,10 +3946,10 @@ impl EditorElement { } } - fn paint_inline_blame(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) { - if let Some(mut inline_blame) = layout.inline_blame.take() { + fn paint_active_line_trailer(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) { + if let Some(mut element) = layout.active_line_trailer.take() { cx.paint_layer(layout.text_hitbox.bounds, |cx| { - inline_blame.paint(cx); + element.paint(cx); }) } } @@ -5331,14 +5341,14 @@ impl Element for EditorElement { ) }); - let mut inline_blame = None; + let mut active_line_trailer = None; if let Some(newest_selection_head) = newest_selection_head { let display_row = newest_selection_head.row(); if (start_row..end_row).contains(&display_row) { let line_ix = display_row.minus(start_row) as usize; let line_layout = &line_layouts[line_ix]; let crease_trailer_layout = crease_trailers[line_ix].as_ref(); - inline_blame = self.layout_inline_blame( + active_line_trailer = self.layout_active_line_trailer( display_row, &snapshot.display_snapshot, line_layout, @@ -5657,7 +5667,7 @@ impl Element for EditorElement { line_elements, line_numbers, blamed_display_rows, - inline_blame, + active_line_trailer, blocks, cursors, visible_cursors, @@ -5794,7 +5804,7 @@ pub struct EditorLayout { line_numbers: Vec>, display_hunks: Vec<(DisplayDiffHunk, Option)>, blamed_display_rows: Option>, - inline_blame: Option, + active_line_trailer: Option, blocks: Vec, highlighted_ranges: Vec<(Range, Hsla)>, highlighted_gutter_ranges: Vec<(Range, Hsla)>, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 0f2be2497a..9a028c1f01 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3050,7 +3050,7 @@ impl<'a> WindowContext<'a> { } /// Represent this action as a key binding string, to display in the UI. - pub fn keystroke_text_for(&self, action: &dyn Action) -> String { + pub fn keystroke_text_for_action(&self, action: &dyn Action) -> String { self.bindings_for_action(action) .into_iter() .next() @@ -3065,6 +3065,26 @@ impl<'a> WindowContext<'a> { .unwrap_or_else(|| action.name().to_string()) } + /// Represent this action as a key binding string, to display in the UI. + pub fn keystroke_text_for_action_in( + &self, + action: &dyn Action, + focus_handle: &FocusHandle, + ) -> String { + self.bindings_for_action_in(action, focus_handle) + .into_iter() + .next() + .map(|binding| { + binding + .keystrokes() + .iter() + .map(ToString::to_string) + .collect::>() + .join(" ") + }) + .unwrap_or_else(|| action.name().to_string()) + } + /// Dispatch a mouse or keyboard event on the window. #[profiling::function] pub fn dispatch_event(&mut self, event: PlatformInput) -> DispatchEventResult { diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index f878b582d9..f378348782 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -3875,13 +3875,13 @@ impl OutlinePanel { .child({ let keystroke = match self.position(cx) { DockPosition::Left => { - cx.keystroke_text_for(&workspace::ToggleLeftDock) + cx.keystroke_text_for_action(&workspace::ToggleLeftDock) } DockPosition::Bottom => { - cx.keystroke_text_for(&workspace::ToggleBottomDock) + cx.keystroke_text_for_action(&workspace::ToggleBottomDock) } DockPosition::Right => { - cx.keystroke_text_for(&workspace::ToggleRightDock) + cx.keystroke_text_for_action(&workspace::ToggleRightDock) } }; Label::new(format!("Toggle this panel with {keystroke}")) diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 072e8ba695..e01309cacd 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -185,13 +185,13 @@ impl PickerDelegate for RecentProjectsDelegate { fn placeholder_text(&self, cx: &mut WindowContext) -> Arc { let (create_window, reuse_window) = if self.create_new_window { ( - cx.keystroke_text_for(&menu::Confirm), - cx.keystroke_text_for(&menu::SecondaryConfirm), + cx.keystroke_text_for_action(&menu::Confirm), + cx.keystroke_text_for_action(&menu::SecondaryConfirm), ) } else { ( - cx.keystroke_text_for(&menu::SecondaryConfirm), - cx.keystroke_text_for(&menu::Confirm), + cx.keystroke_text_for_action(&menu::SecondaryConfirm), + cx.keystroke_text_for_action(&menu::Confirm), ) }; Arc::from(format!( diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index a5fc52e933..c632843baa 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -66,7 +66,7 @@ use zed::{ OpenRequest, }; -use crate::zed::inline_completion_registry; +use crate::zed::{assistant_hints, inline_completion_registry}; #[cfg(feature = "mimalloc")] #[global_allocator] @@ -401,6 +401,7 @@ fn main() { stdout_is_a_pty(), cx, ); + assistant_hints::init(cx); repl::init( app_state.fs.clone(), app_state.client.telemetry().clone(), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e2dc36a21f..0f10f1914b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1,4 +1,5 @@ mod app_menus; +pub mod assistant_hints; pub mod inline_completion_registry; #[cfg(any(target_os = "linux", target_os = "freebsd"))] pub(crate) mod linux_prompts; diff --git a/crates/zed/src/zed/assistant_hints.rs b/crates/zed/src/zed/assistant_hints.rs new file mode 100644 index 0000000000..244b7fab26 --- /dev/null +++ b/crates/zed/src/zed/assistant_hints.rs @@ -0,0 +1,115 @@ +use assistant::assistant_settings::AssistantSettings; +use collections::HashMap; +use editor::{ActiveLineTrailerProvider, Editor, EditorMode}; +use gpui::{AnyWindowHandle, AppContext, ViewContext, WeakView, WindowContext}; +use settings::{Settings, SettingsStore}; +use std::{cell::RefCell, rc::Rc}; +use theme::ActiveTheme; +use ui::prelude::*; +use workspace::Workspace; + +pub fn init(cx: &mut AppContext) { + let editors: Rc, AnyWindowHandle>>> = Rc::default(); + + cx.observe_new_views({ + let editors = editors.clone(); + move |_: &mut Workspace, cx: &mut ViewContext| { + let workspace_handle = cx.view().clone(); + cx.subscribe(&workspace_handle, { + let editors = editors.clone(); + move |_, _, event, cx| match event { + workspace::Event::ItemAdded { item } => { + if let Some(editor) = item.act_as::(cx) { + if editor.read(cx).mode() != EditorMode::Full { + return; + } + + cx.on_release({ + let editor_handle = editor.downgrade(); + let editors = editors.clone(); + move |_, _, _| { + editors.borrow_mut().remove(&editor_handle); + } + }) + .detach(); + editors + .borrow_mut() + .insert(editor.downgrade(), cx.window_handle()); + + let show_hints = should_show_hints(cx); + editor.update(cx, |editor, cx| { + assign_active_line_trailer_provider(editor, show_hints, cx) + }) + } + } + _ => {} + } + }) + .detach(); + } + }) + .detach(); + + let mut show_hints = AssistantSettings::get_global(cx).show_hints; + cx.observe_global::(move |cx| { + let new_show_hints = should_show_hints(cx); + if new_show_hints != show_hints { + show_hints = new_show_hints; + for (editor, window) in editors.borrow().iter() { + _ = window.update(cx, |_window, cx| { + _ = editor.update(cx, |editor, cx| { + assign_active_line_trailer_provider(editor, show_hints, cx); + }) + }); + } + } + }) + .detach(); +} + +struct AssistantHintsProvider; + +impl ActiveLineTrailerProvider for AssistantHintsProvider { + fn render_active_line_trailer( + &mut self, + style: &editor::EditorStyle, + focus_handle: &gpui::FocusHandle, + cx: &mut WindowContext, + ) -> Option { + if !focus_handle.is_focused(cx) { + return None; + } + + let chat_keybinding = + cx.keystroke_text_for_action_in(&assistant::ToggleFocus, focus_handle); + let generate_keybinding = + cx.keystroke_text_for_action_in(&zed_actions::InlineAssist::default(), focus_handle); + + Some( + h_flex() + .id("inline-assistant-instructions") + .w_full() + .font_family(style.text.font().family) + .text_color(cx.theme().status().hint) + .line_height(style.text.line_height) + .child(format!( + "{chat_keybinding} to chat, {generate_keybinding} to generate" + )) + .into_any(), + ) + } +} + +fn assign_active_line_trailer_provider( + editor: &mut Editor, + show_hints: bool, + cx: &mut ViewContext, +) { + let provider = show_hints.then_some(AssistantHintsProvider); + editor.set_active_line_trailer_provider(provider, cx); +} + +fn should_show_hints(cx: &AppContext) -> bool { + let assistant_settings = AssistantSettings::get_global(cx); + assistant_settings.enabled && assistant_settings.show_hints +} diff --git a/docs/src/assistant/configuration.md b/docs/src/assistant/configuration.md index 2145bd9504..1be96491f4 100644 --- a/docs/src/assistant/configuration.md +++ b/docs/src/assistant/configuration.md @@ -200,18 +200,28 @@ You must provide the model's Context Window in the `max_tokens` parameter, this { "assistant": { "enabled": true, + "show_hints": true, + "button": true, + "dock": "right" + "default_width": 480, "default_model": { "provider": "zed.dev", "model": "claude-3-5-sonnet" }, "version": "2", - "button": true, - "default_width": 480, - "dock": "right" } } ``` +| key | type | default | description | +| -------------- | ------- | ------- | ------------------------------------------------------------------------------------- | +| enabled | boolean | true | Setting this to `false` will completely disable the assistant | +| show_hints | boolean | true | Whether to to show hints in the editor explaining how to use assistant | +| button | boolean | true | Show the assistant icon in the status bar | +| dock | string | "right" | The default dock position for the assistant panel. Can be ["left", "right", "bottom"] | +| default_height | string | null | The pixel height of the assistant panel when docked to the bottom | +| default_width | string | null | The pixel width of the assistant panel when docked to the left or right | + #### Custom endpoints {#custom-endpoint} You can use a custom API endpoint for different providers, as long as it's compatible with the providers API structure. @@ -271,13 +281,3 @@ will generate two outputs for every assist. One with Claude 3.5 Sonnet, and one } } ``` - -#### Common Panel Settings - -| key | type | default | description | -| -------------- | ------- | ------- | ------------------------------------------------------------------------------------- | -| enabled | boolean | true | Setting this to `false` will completely disable the assistant | -| button | boolean | true | Show the assistant icon in the status bar | -| dock | string | "right" | The default dock position for the assistant panel. Can be ["left", "right", "bottom"] | -| default_height | string | null | The pixel height of the assistant panel when docked to the bottom | -| default_width | string | null | The pixel width of the assistant panel when docked to the left or right | diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index ce8068fa3b..b4da7901a1 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -2327,15 +2327,18 @@ Run the `theme selector: toggle` action in the command palette to see a current - Default: ```json -"assistant": { - "enabled": true, - "button": true, - "dock": "right", - "default_width": 640, - "default_height": 320, - "provider": "openai", - "version": "1", -}, +{ + "assistant": { + "enabled": true, + "button": true, + "dock": "right", + "default_width": 640, + "default_height": 320, + "provider": "openai", + "version": "1", + "show_hints": true + } +} ``` ## Outline Panel From 5b0c15d8c436868e62b6123421cb28e2c70fc420 Mon Sep 17 00:00:00 2001 From: Julian de Ruiter <307739+jrderuiter@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:34:56 +0100 Subject: [PATCH 050/157] Add pytest-based test discovery and runnables for Python (#18824) Closes #12080, #18649. Screenshot: image Still in progress: 1. I'd like to add configuration options for selecting a Python test runner (either pytest or unittest) so that users can explicitly choose which runner they'd like to use for running their tests. This preference has to be configured as unittest-style tests can also be run by pytest, meaning we can't rely on auto-discovery to choose the desired test runner. 2. I'd like to add venv auto-discovery similar to the feature currently provided by the terminal using detect_venv. 3. Unit tests. Unfortunately I'm struggling a bit with how to add settings in the appropriate location (e.g. Python language settings). Can anyone provide me with some pointers and/or examples on how to either add extra settings or to re-use the existing ones? My rust programming level is OK-ish but I'm not very familiar with the Zed project structure and could use some help. I'm also open for pair programming as mentioned on the website if that helps! Release Notes: - Added pytest-based test discovery and runnables for Python. - Adds a configurable option for switching between unittest and pytest as a test runner under Python language settings. Set "TASK_RUNNER" to "unittest" under task settings for Python if you wish to use unittest to run Python tasks; the default is pytest. --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> --- crates/languages/src/python.rs | 228 +++++++++++++++++----- crates/languages/src/python/runnables.scm | 39 ++++ 2 files changed, 217 insertions(+), 50 deletions(-) diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 1e855777b2..a29eb1c679 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -4,6 +4,7 @@ use async_trait::async_trait; use collections::HashMap; use gpui::AsyncAppContext; use gpui::{AppContext, Task}; +use language::language_settings::language_settings; use language::LanguageName; use language::LanguageToolchainStore; use language::Toolchain; @@ -21,6 +22,7 @@ use serde_json::{json, Value}; use smol::{lock::OnceCell, process::Command}; use std::cmp::Ordering; +use std::str::FromStr; use std::sync::Mutex; use std::{ any::Any, @@ -35,6 +37,23 @@ use util::ResultExt; const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js"; const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js"; +enum TestRunner { + UNITTEST, + PYTEST, +} + +impl FromStr for TestRunner { + type Err = (); + + fn from_str(s: &str) -> std::result::Result { + match s { + "unittest" => Ok(Self::UNITTEST), + "pytest" => Ok(Self::PYTEST), + _ => Err(()), + } + } +} + fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] } @@ -265,8 +284,8 @@ async fn get_cached_server_binary( pub(crate) struct PythonContextProvider; -const PYTHON_UNITTEST_TARGET_TASK_VARIABLE: VariableName = - VariableName::Custom(Cow::Borrowed("PYTHON_UNITTEST_TARGET")); +const PYTHON_TEST_TARGET_TASK_VARIABLE: VariableName = + VariableName::Custom(Cow::Borrowed("PYTHON_TEST_TARGET")); const PYTHON_ACTIVE_TOOLCHAIN_PATH: VariableName = VariableName::Custom(Cow::Borrowed("PYTHON_ACTIVE_ZED_TOOLCHAIN")); @@ -279,28 +298,16 @@ impl ContextProvider for PythonContextProvider { toolchains: Arc, cx: &mut gpui::AppContext, ) -> Task> { - let python_module_name = python_module_name_from_relative_path( - variables.get(&VariableName::RelativeFile).unwrap_or(""), - ); - let unittest_class_name = - variables.get(&VariableName::Custom(Cow::Borrowed("_unittest_class_name"))); - let unittest_method_name = variables.get(&VariableName::Custom(Cow::Borrowed( - "_unittest_method_name", - ))); + let test_target = { + let test_runner = selected_test_runner(location.buffer.read(cx).file(), cx); - let unittest_target_str = match (unittest_class_name, unittest_method_name) { - (Some(class_name), Some(method_name)) => { - format!("{}.{}.{}", python_module_name, class_name, method_name) - } - (Some(class_name), None) => format!("{}.{}", python_module_name, class_name), - (None, None) => python_module_name, - (None, Some(_)) => return Task::ready(Ok(task::TaskVariables::default())), // should never happen, a TestCase class is the unit of testing + let runner = match test_runner { + TestRunner::UNITTEST => self.build_unittest_target(variables), + TestRunner::PYTEST => self.build_pytest_target(variables), + }; + runner }; - let unittest_target = ( - PYTHON_UNITTEST_TARGET_TASK_VARIABLE.clone(), - unittest_target_str, - ); let worktree_id = location.buffer.read(cx).file().map(|f| f.worktree_id(cx)); cx.spawn(move |mut cx| async move { let active_toolchain = if let Some(worktree_id) = worktree_id { @@ -312,53 +319,174 @@ impl ContextProvider for PythonContextProvider { String::from("python3") }; let toolchain = (PYTHON_ACTIVE_TOOLCHAIN_PATH, active_toolchain); - Ok(task::TaskVariables::from_iter([unittest_target, toolchain])) + Ok(task::TaskVariables::from_iter([test_target?, toolchain])) }) } fn associated_tasks( &self, - _: Option>, - _: &AppContext, + file: Option>, + cx: &AppContext, ) -> Option { - Some(TaskTemplates(vec![ + let test_runner = selected_test_runner(file.as_ref(), cx); + + let mut tasks = vec![ + // Execute a selection TaskTemplate { label: "execute selection".to_owned(), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec!["-c".to_owned(), VariableName::SelectedText.template_value()], ..TaskTemplate::default() }, + // Execute an entire file TaskTemplate { label: format!("run '{}'", VariableName::File.template_value()), command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), args: vec![VariableName::File.template_value()], ..TaskTemplate::default() }, - TaskTemplate { - label: format!("unittest '{}'", VariableName::File.template_value()), - command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), - args: vec![ - "-m".to_owned(), - "unittest".to_owned(), - VariableName::File.template_value(), - ], - ..TaskTemplate::default() - }, - TaskTemplate { - label: "unittest $ZED_CUSTOM_PYTHON_UNITTEST_TARGET".to_owned(), - command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), - args: vec![ - "-m".to_owned(), - "unittest".to_owned(), - "$ZED_CUSTOM_PYTHON_UNITTEST_TARGET".to_owned(), - ], - tags: vec![ - "python-unittest-class".to_owned(), - "python-unittest-method".to_owned(), - ], - ..TaskTemplate::default() - }, - ])) + ]; + + tasks.extend(match test_runner { + TestRunner::UNITTEST => { + [ + // Run tests for an entire file + TaskTemplate { + label: format!("unittest '{}'", VariableName::File.template_value()), + command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), + args: vec![ + "-m".to_owned(), + "unittest".to_owned(), + VariableName::File.template_value(), + ], + ..TaskTemplate::default() + }, + // Run test(s) for a specific target within a file + TaskTemplate { + label: "unittest $ZED_CUSTOM_PYTHON_TEST_TARGET".to_owned(), + command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), + args: vec![ + "-m".to_owned(), + "unittest".to_owned(), + "$ZED_CUSTOM_PYTHON_TEST_TARGET".to_owned(), + ], + tags: vec![ + "python-unittest-class".to_owned(), + "python-unittest-method".to_owned(), + ], + ..TaskTemplate::default() + }, + ] + } + TestRunner::PYTEST => { + [ + // Run tests for an entire file + TaskTemplate { + label: format!("pytest '{}'", VariableName::File.template_value()), + command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), + args: vec![ + "-m".to_owned(), + "pytest".to_owned(), + VariableName::File.template_value(), + ], + ..TaskTemplate::default() + }, + // Run test(s) for a specific target within a file + TaskTemplate { + label: "pytest $ZED_CUSTOM_PYTHON_TEST_TARGET".to_owned(), + command: PYTHON_ACTIVE_TOOLCHAIN_PATH.template_value(), + args: vec![ + "-m".to_owned(), + "pytest".to_owned(), + "$ZED_CUSTOM_PYTHON_TEST_TARGET".to_owned(), + ], + tags: vec![ + "python-pytest-class".to_owned(), + "python-pytest-method".to_owned(), + ], + ..TaskTemplate::default() + }, + ] + } + }); + + Some(TaskTemplates(tasks)) + } +} + +fn selected_test_runner(location: Option<&Arc>, cx: &AppContext) -> TestRunner { + const TEST_RUNNER_VARIABLE: &str = "TEST_RUNNER"; + language_settings(Some(LanguageName::new("Python")), location, cx) + .tasks + .variables + .get(TEST_RUNNER_VARIABLE) + .and_then(|val| TestRunner::from_str(val).ok()) + .unwrap_or(TestRunner::PYTEST) +} + +impl PythonContextProvider { + fn build_unittest_target( + &self, + variables: &task::TaskVariables, + ) -> Result<(VariableName, String)> { + let python_module_name = python_module_name_from_relative_path( + variables.get(&VariableName::RelativeFile).unwrap_or(""), + ); + + let unittest_class_name = + variables.get(&VariableName::Custom(Cow::Borrowed("_unittest_class_name"))); + + let unittest_method_name = variables.get(&VariableName::Custom(Cow::Borrowed( + "_unittest_method_name", + ))); + + let unittest_target_str = match (unittest_class_name, unittest_method_name) { + (Some(class_name), Some(method_name)) => { + format!("{}.{}.{}", python_module_name, class_name, method_name) + } + (Some(class_name), None) => format!("{}.{}", python_module_name, class_name), + (None, None) => python_module_name, + (None, Some(_)) => return Ok((VariableName::Custom(Cow::Borrowed("")), String::new())), // should never happen, a TestCase class is the unit of testing + }; + + let unittest_target = ( + PYTHON_TEST_TARGET_TASK_VARIABLE.clone(), + unittest_target_str, + ); + + Ok(unittest_target) + } + + fn build_pytest_target( + &self, + variables: &task::TaskVariables, + ) -> Result<(VariableName, String)> { + let file_path = variables + .get(&VariableName::RelativeFile) + .ok_or_else(|| anyhow!("No file path given"))?; + + let pytest_class_name = + variables.get(&VariableName::Custom(Cow::Borrowed("_pytest_class_name"))); + + let pytest_method_name = + variables.get(&VariableName::Custom(Cow::Borrowed("_pytest_method_name"))); + + let pytest_target_str = match (pytest_class_name, pytest_method_name) { + (Some(class_name), Some(method_name)) => { + format!("{}::{}::{}", file_path, class_name, method_name) + } + (Some(class_name), None) => { + format!("{}::{}", file_path, class_name) + } + (None, Some(method_name)) => { + format!("{}::{}", file_path, method_name) + } + (None, None) => file_path.to_string(), + }; + + let pytest_target = (PYTHON_TEST_TARGET_TASK_VARIABLE.clone(), pytest_target_str); + + Ok(pytest_target) } } diff --git a/crates/languages/src/python/runnables.scm b/crates/languages/src/python/runnables.scm index b9bc5e9bf2..31994dfa2c 100644 --- a/crates/languages/src/python/runnables.scm +++ b/crates/languages/src/python/runnables.scm @@ -29,3 +29,42 @@ ) ) ) + +; pytest functions +( + (module + (function_definition + name: (identifier) @run @_pytest_method_name + (#match? @_pytest_method_name "^test_") + ) @python-pytest-method + ) + (#set! tag python-pytest-method) +) + +; pytest classes +( + (module + (class_definition + name: (identifier) @run @_pytest_class_name + (#match? @_pytest_class_name "^Test") + ) + (#set! tag python-pytest-class) + ) +) + +; pytest class methods +( + (module + (class_definition + name: (identifier) @_pytest_class_name + (#match? @_pytest_class_name "^Test") + body: (block + (function_definition + name: (identifier) @run @_pytest_method_name + (#match? @_pytest_method_name "^test") + ) @python-pytest-method + (#set! tag python-pytest-method) + ) + ) + ) +) From 9454f0f1c7361da0344c5c9c3d876084cda1772b Mon Sep 17 00:00:00 2001 From: Egor Krugletsov <74310448+Poldraunic@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:49:21 +0300 Subject: [PATCH 051/157] clangd: Use Url::to_file_path() to get actual file path for header/source (#20856) Using `Url::path()` seems fine on POSIX systems as it will leave forward slash (given hostname is empty). On Windows it will result in error. Release Notes: - N/A --- crates/editor/src/clangd_ext.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/clangd_ext.rs b/crates/editor/src/clangd_ext.rs index 501f81b107..c018362068 100644 --- a/crates/editor/src/clangd_ext.rs +++ b/crates/editor/src/clangd_ext.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use anyhow::Context as _; use gpui::{View, ViewContext, WindowContext}; use language::Language; @@ -54,9 +52,9 @@ pub fn switch_source_header( cx.spawn(|_editor, mut cx| async move { let switch_source_header = switch_source_header_task .await - .with_context(|| format!("Switch source/header LSP request for path \"{}\" failed", source_file))?; + .with_context(|| format!("Switch source/header LSP request for path \"{source_file}\" failed"))?; if switch_source_header.0.is_empty() { - log::info!("Clangd returned an empty string when requesting to switch source/header from \"{}\"", source_file); + log::info!("Clangd returned an empty string when requesting to switch source/header from \"{source_file}\"" ); return Ok(()); } @@ -67,14 +65,17 @@ pub fn switch_source_header( ) })?; + let path = goto.to_file_path().map_err(|()| { + anyhow::anyhow!("URL conversion to file path failed for \"{goto}\"") + })?; + workspace .update(&mut cx, |workspace, view_cx| { - workspace.open_abs_path(PathBuf::from(goto.path()), false, view_cx) + workspace.open_abs_path(path, false, view_cx) }) .with_context(|| { format!( - "Switch source/header could not open \"{}\" in workspace", - goto.path() + "Switch source/header could not open \"{goto}\" in workspace" ) })? .await From 6a2c7129908389c772f15a4890e83e2bea4a7e3c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 19 Nov 2024 08:23:12 -0700 Subject: [PATCH 052/157] Use Instant not chrono for telemetry (#20756) We occasionally see dates in the future appearing in our telemetry. One hypothesis is that this is caused by a clock change while Zed is running causing date math based on chrono to be incorrect. Instant *should* be a more stable source of relative timestamps. Release Notes: - N/A --- Cargo.lock | 1 - crates/channel/src/channel_store_tests.rs | 2 +- crates/client/src/client.rs | 12 +++--- crates/client/src/telemetry.rs | 31 +++++++-------- .../client/src/telemetry/event_coalescer.rs | 39 +++++++------------ crates/clock/Cargo.toml | 1 - crates/clock/src/system_clock.rs | 30 ++++++-------- crates/collab/src/tests/test_server.rs | 2 +- crates/project/src/project.rs | 4 +- .../remote_server/src/remote_editing_tests.rs | 2 +- crates/semantic_index/examples/index.rs | 2 +- crates/workspace/src/workspace.rs | 2 +- 12 files changed, 54 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f5934fca8..38de2f5c00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2473,7 +2473,6 @@ dependencies = [ name = "clock" version = "0.1.0" dependencies = [ - "chrono", "parking_lot", "serde", "smallvec", diff --git a/crates/channel/src/channel_store_tests.rs b/crates/channel/src/channel_store_tests.rs index 1cf9fa706d..11f618d196 100644 --- a/crates/channel/src/channel_store_tests.rs +++ b/crates/channel/src/channel_store_tests.rs @@ -343,7 +343,7 @@ fn init_test(cx: &mut AppContext) -> Model { release_channel::init(SemanticVersion::default(), cx); client::init_settings(cx); - let clock = Arc::new(FakeSystemClock::default()); + let clock = Arc::new(FakeSystemClock::new()); let http = FakeHttpClient::with_404_response(); let client = Client::new(clock, http.clone(), cx); let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx)); diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 1e73c7be66..041973e884 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1780,7 +1780,7 @@ mod tests { let user_id = 5; let client = cx.update(|cx| { Client::new( - Arc::new(FakeSystemClock::default()), + Arc::new(FakeSystemClock::new()), FakeHttpClient::with_404_response(), cx, ) @@ -1821,7 +1821,7 @@ mod tests { let user_id = 5; let client = cx.update(|cx| { Client::new( - Arc::new(FakeSystemClock::default()), + Arc::new(FakeSystemClock::new()), FakeHttpClient::with_404_response(), cx, ) @@ -1900,7 +1900,7 @@ mod tests { let dropped_auth_count = Arc::new(Mutex::new(0)); let client = cx.update(|cx| { Client::new( - Arc::new(FakeSystemClock::default()), + Arc::new(FakeSystemClock::new()), FakeHttpClient::with_404_response(), cx, ) @@ -1943,7 +1943,7 @@ mod tests { let user_id = 5; let client = cx.update(|cx| { Client::new( - Arc::new(FakeSystemClock::default()), + Arc::new(FakeSystemClock::new()), FakeHttpClient::with_404_response(), cx, ) @@ -2003,7 +2003,7 @@ mod tests { let user_id = 5; let client = cx.update(|cx| { Client::new( - Arc::new(FakeSystemClock::default()), + Arc::new(FakeSystemClock::new()), FakeHttpClient::with_404_response(), cx, ) @@ -2038,7 +2038,7 @@ mod tests { let user_id = 5; let client = cx.update(|cx| { Client::new( - Arc::new(FakeSystemClock::default()), + Arc::new(FakeSystemClock::new()), FakeHttpClient::with_404_response(), cx, ) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 27a49a9816..b472cf768e 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -2,7 +2,6 @@ mod event_coalescer; use crate::{ChannelId, TelemetrySettings}; use anyhow::Result; -use chrono::{DateTime, Utc}; use clock::SystemClock; use collections::{HashMap, HashSet}; use futures::Future; @@ -15,6 +14,7 @@ use settings::{Settings, SettingsStore}; use sha2::{Digest, Sha256}; use std::fs::File; use std::io::Write; +use std::time::Instant; use std::{env, mem, path::PathBuf, sync::Arc, time::Duration}; use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System}; use telemetry_events::{ @@ -46,7 +46,7 @@ struct TelemetryState { flush_events_task: Option>, log_file: Option, is_staff: Option, - first_event_date_time: Option>, + first_event_date_time: Option, event_coalescer: EventCoalescer, max_queue_size: usize, worktree_id_map: WorktreeIdMap, @@ -469,7 +469,10 @@ impl Telemetry { if let Some((start, end, environment)) = period_data { let event = Event::Edit(EditEvent { - duration: end.timestamp_millis() - start.timestamp_millis(), + duration: end + .saturating_duration_since(start) + .min(Duration::from_secs(60 * 60 * 24)) + .as_millis() as i64, environment: environment.to_string(), is_via_ssh, }); @@ -567,9 +570,10 @@ impl Telemetry { let date_time = self.clock.utc_now(); let milliseconds_since_first_event = match state.first_event_date_time { - Some(first_event_date_time) => { - date_time.timestamp_millis() - first_event_date_time.timestamp_millis() - } + Some(first_event_date_time) => date_time + .saturating_duration_since(first_event_date_time) + .min(Duration::from_secs(60 * 60 * 24)) + .as_millis() as i64, None => { state.first_event_date_time = Some(date_time); 0 @@ -702,7 +706,6 @@ pub fn calculate_json_checksum(json: &impl AsRef<[u8]>) -> Option { #[cfg(test)] mod tests { use super::*; - use chrono::TimeZone; use clock::FakeSystemClock; use gpui::TestAppContext; use http_client::FakeHttpClient; @@ -710,9 +713,7 @@ mod tests { #[gpui::test] fn test_telemetry_flush_on_max_queue_size(cx: &mut TestAppContext) { init_test(cx); - let clock = Arc::new(FakeSystemClock::new( - Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(), - )); + let clock = Arc::new(FakeSystemClock::new()); let http = FakeHttpClient::with_200_response(); let system_id = Some("system_id".to_string()); let installation_id = Some("installation_id".to_string()); @@ -743,7 +744,7 @@ mod tests { Some(first_date_time) ); - clock.advance(chrono::Duration::milliseconds(100)); + clock.advance(Duration::from_millis(100)); let event = telemetry.report_app_event(operation.clone()); assert_eq!( @@ -759,7 +760,7 @@ mod tests { Some(first_date_time) ); - clock.advance(chrono::Duration::milliseconds(100)); + clock.advance(Duration::from_millis(100)); let event = telemetry.report_app_event(operation.clone()); assert_eq!( @@ -775,7 +776,7 @@ mod tests { Some(first_date_time) ); - clock.advance(chrono::Duration::milliseconds(100)); + clock.advance(Duration::from_millis(100)); // Adding a 4th event should cause a flush let event = telemetry.report_app_event(operation.clone()); @@ -796,9 +797,7 @@ mod tests { cx: &mut TestAppContext, ) { init_test(cx); - let clock = Arc::new(FakeSystemClock::new( - Utc.with_ymd_and_hms(1990, 4, 12, 12, 0, 0).unwrap(), - )); + let clock = Arc::new(FakeSystemClock::new()); let http = FakeHttpClient::with_200_response(); let system_id = Some("system_id".to_string()); let installation_id = Some("installation_id".to_string()); diff --git a/crates/client/src/telemetry/event_coalescer.rs b/crates/client/src/telemetry/event_coalescer.rs index 33bcf492f6..e58112ac08 100644 --- a/crates/client/src/telemetry/event_coalescer.rs +++ b/crates/client/src/telemetry/event_coalescer.rs @@ -1,7 +1,6 @@ -use std::sync::Arc; use std::time; +use std::{sync::Arc, time::Instant}; -use chrono::{DateTime, Duration, Utc}; use clock::SystemClock; const COALESCE_TIMEOUT: time::Duration = time::Duration::from_secs(20); @@ -10,8 +9,8 @@ const SIMULATED_DURATION_FOR_SINGLE_EVENT: time::Duration = time::Duration::from #[derive(Debug, PartialEq)] struct PeriodData { environment: &'static str, - start: DateTime, - end: Option>, + start: Instant, + end: Option, } pub struct EventCoalescer { @@ -27,9 +26,8 @@ impl EventCoalescer { pub fn log_event( &mut self, environment: &'static str, - ) -> Option<(DateTime, DateTime, &'static str)> { + ) -> Option<(Instant, Instant, &'static str)> { let log_time = self.clock.utc_now(); - let coalesce_timeout = Duration::from_std(COALESCE_TIMEOUT).unwrap(); let Some(state) = &mut self.state else { self.state = Some(PeriodData { @@ -43,7 +41,7 @@ impl EventCoalescer { let period_end = state .end .unwrap_or(state.start + SIMULATED_DURATION_FOR_SINGLE_EVENT); - let within_timeout = log_time - period_end < coalesce_timeout; + let within_timeout = log_time - period_end < COALESCE_TIMEOUT; let environment_is_same = state.environment == environment; let should_coaelesce = !within_timeout || !environment_is_same; @@ -70,16 +68,13 @@ impl EventCoalescer { #[cfg(test)] mod tests { - use chrono::TimeZone; use clock::FakeSystemClock; use super::*; #[test] fn test_same_context_exceeding_timeout() { - let clock = Arc::new(FakeSystemClock::new( - Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(), - )); + let clock = Arc::new(FakeSystemClock::new()); let environment_1 = "environment_1"; let mut event_coalescer = EventCoalescer::new(clock.clone()); @@ -98,7 +93,7 @@ mod tests { }) ); - let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); + let within_timeout_adjustment = COALESCE_TIMEOUT / 2; // Ensure that many calls within the timeout don't start a new period for _ in 0..100 { @@ -118,7 +113,7 @@ mod tests { } let period_end = clock.utc_now(); - let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); + let exceed_timeout_adjustment = COALESCE_TIMEOUT * 2; // Logging an event exceeding the timeout should start a new period clock.advance(exceed_timeout_adjustment); let new_period_start = clock.utc_now(); @@ -137,9 +132,7 @@ mod tests { #[test] fn test_different_environment_under_timeout() { - let clock = Arc::new(FakeSystemClock::new( - Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(), - )); + let clock = Arc::new(FakeSystemClock::new()); let environment_1 = "environment_1"; let mut event_coalescer = EventCoalescer::new(clock.clone()); @@ -158,7 +151,7 @@ mod tests { }) ); - let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); + let within_timeout_adjustment = COALESCE_TIMEOUT / 2; clock.advance(within_timeout_adjustment); let period_end = clock.utc_now(); let period_data = event_coalescer.log_event(environment_1); @@ -193,9 +186,7 @@ mod tests { #[test] fn test_switching_environment_while_within_timeout() { - let clock = Arc::new(FakeSystemClock::new( - Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(), - )); + let clock = Arc::new(FakeSystemClock::new()); let environment_1 = "environment_1"; let mut event_coalescer = EventCoalescer::new(clock.clone()); @@ -214,7 +205,7 @@ mod tests { }) ); - let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); + let within_timeout_adjustment = COALESCE_TIMEOUT / 2; clock.advance(within_timeout_adjustment); let period_end = clock.utc_now(); let environment_2 = "environment_2"; @@ -240,9 +231,7 @@ mod tests { #[test] fn test_switching_environment_while_exceeding_timeout() { - let clock = Arc::new(FakeSystemClock::new( - Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(), - )); + let clock = Arc::new(FakeSystemClock::new()); let environment_1 = "environment_1"; let mut event_coalescer = EventCoalescer::new(clock.clone()); @@ -261,7 +250,7 @@ mod tests { }) ); - let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); + let exceed_timeout_adjustment = COALESCE_TIMEOUT * 2; clock.advance(exceed_timeout_adjustment); let period_end = clock.utc_now(); let environment_2 = "environment_2"; diff --git a/crates/clock/Cargo.toml b/crates/clock/Cargo.toml index 699a50e70d..b6f28741c3 100644 --- a/crates/clock/Cargo.toml +++ b/crates/clock/Cargo.toml @@ -16,7 +16,6 @@ doctest = false test-support = ["dep:parking_lot"] [dependencies] -chrono.workspace = true parking_lot = { workspace = true, optional = true } serde.workspace = true smallvec.workspace = true diff --git a/crates/clock/src/system_clock.rs b/crates/clock/src/system_clock.rs index a462ffc35b..b8e50d0b27 100644 --- a/crates/clock/src/system_clock.rs +++ b/crates/clock/src/system_clock.rs @@ -1,21 +1,21 @@ -use chrono::{DateTime, Utc}; +use std::time::Instant; pub trait SystemClock: Send + Sync { /// Returns the current date and time in UTC. - fn utc_now(&self) -> DateTime; + fn utc_now(&self) -> Instant; } pub struct RealSystemClock; impl SystemClock for RealSystemClock { - fn utc_now(&self) -> DateTime { - Utc::now() + fn utc_now(&self) -> Instant { + Instant::now() } } #[cfg(any(test, feature = "test-support"))] pub struct FakeSystemClockState { - now: DateTime, + now: Instant, } #[cfg(any(test, feature = "test-support"))] @@ -24,36 +24,30 @@ pub struct FakeSystemClock { state: parking_lot::Mutex, } -#[cfg(any(test, feature = "test-support"))] -impl Default for FakeSystemClock { - fn default() -> Self { - Self::new(Utc::now()) - } -} - #[cfg(any(test, feature = "test-support"))] impl FakeSystemClock { - pub fn new(now: DateTime) -> Self { - let state = FakeSystemClockState { now }; + pub fn new() -> Self { + let state = FakeSystemClockState { + now: Instant::now(), + }; Self { state: parking_lot::Mutex::new(state), } } - pub fn set_now(&self, now: DateTime) { + pub fn set_now(&self, now: Instant) { self.state.lock().now = now; } - /// Advances the [`FakeSystemClock`] by the specified [`Duration`](chrono::Duration). - pub fn advance(&self, duration: chrono::Duration) { + pub fn advance(&self, duration: std::time::Duration) { self.state.lock().now += duration; } } #[cfg(any(test, feature = "test-support"))] impl SystemClock for FakeSystemClock { - fn utc_now(&self) -> DateTime { + fn utc_now(&self) -> Instant { self.state.lock().now } } diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 17cd1b51c4..8a09f06092 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -168,7 +168,7 @@ impl TestServer { client::init_settings(cx); }); - let clock = Arc::new(FakeSystemClock::default()); + let clock = Arc::new(FakeSystemClock::new()); let http = FakeHttpClient::with_404_response(); let user_id = if let Ok(Some(user)) = self.app_state.db.get_user_by_github_login(name).await { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 17f84a6f37..2b18659b7d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1133,7 +1133,7 @@ impl Project { let fs = Arc::new(RealFs::default()); let languages = LanguageRegistry::test(cx.background_executor().clone()); - let clock = Arc::new(FakeSystemClock::default()); + let clock = Arc::new(FakeSystemClock::new()); let http_client = http_client::FakeHttpClient::with_404_response(); let client = cx .update(|cx| client::Client::new(clock, http_client.clone(), cx)) @@ -1179,7 +1179,7 @@ impl Project { use gpui::Context; let languages = LanguageRegistry::test(cx.executor()); - let clock = Arc::new(FakeSystemClock::default()); + let clock = Arc::new(FakeSystemClock::new()); let http_client = http_client::FakeHttpClient::with_404_response(); let client = cx.update(|cx| client::Client::new(clock, http_client.clone(), cx)); let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx)); diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index e3914c7ae1..3a9803287a 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -1277,7 +1277,7 @@ fn build_project(ssh: Model, cx: &mut TestAppContext) -> Model< let client = cx.update(|cx| { Client::new( - Arc::new(FakeSystemClock::default()), + Arc::new(FakeSystemClock::new()), FakeHttpClient::with_404_response(), cx, ) diff --git a/crates/semantic_index/examples/index.rs b/crates/semantic_index/examples/index.rs index 2efd94cb57..25e03f5b3a 100644 --- a/crates/semantic_index/examples/index.rs +++ b/crates/semantic_index/examples/index.rs @@ -25,7 +25,7 @@ fn main() { store.update_user_settings::(cx, |_| {}); }); - let clock = Arc::new(FakeSystemClock::default()); + let clock = Arc::new(FakeSystemClock::new()); let http = Arc::new(HttpClientWithUrl::new( Arc::new( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 833a8b15a0..32e441ee50 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -606,7 +606,7 @@ impl AppState { let fs = fs::FakeFs::new(cx.background_executor().clone()); let languages = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); - let clock = Arc::new(clock::FakeSystemClock::default()); + let clock = Arc::new(clock::FakeSystemClock::new()); let http_client = http_client::FakeHttpClient::with_404_response(); let client = Client::new(clock, http_client.clone(), cx); let session = cx.new_model(|cx| AppSession::new(Session::test(), cx)); From f5cbfa718e5bb4a284d0c2bfac6b425e4d7a343d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 19 Nov 2024 11:20:30 -0500 Subject: [PATCH 053/157] assistant: Fix evaluating slash commands in slash command output (like `/default`) (#20864) This PR fixes an issue where slash commands in the output of other slash commands were not being evaluated when configured to do so. Closes https://github.com/zed-industries/zed/issues/20820. Release Notes: - Fixed slash commands from other slash commands (like `/default`) not being evaluated (Preview only). --- crates/assistant/src/assistant_panel.rs | 55 +++++++++++-------- crates/assistant/src/context.rs | 16 ++++-- .../src/slash_command/default_command.rs | 4 ++ 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index b682bfdcca..c89595c7da 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2050,30 +2050,6 @@ impl ContextEditor { ContextEvent::SlashCommandOutputSectionAdded { section } => { self.insert_slash_command_output_sections([section.clone()], false, cx); } - ContextEvent::SlashCommandFinished { - output_range: _output_range, - run_commands_in_ranges, - } => { - for range in run_commands_in_ranges { - let commands = self.context.update(cx, |context, cx| { - context.reparse(cx); - context - .pending_commands_for_range(range.clone(), cx) - .to_vec() - }); - - for command in commands { - self.run_command( - command.source_range, - &command.name, - &command.arguments, - false, - self.workspace.clone(), - cx, - ); - } - } - } ContextEvent::UsePendingTools => { let pending_tool_uses = self .context @@ -2152,6 +2128,37 @@ impl ContextEditor { command_id: InvokedSlashCommandId, cx: &mut ViewContext, ) { + if let Some(invoked_slash_command) = + self.context.read(cx).invoked_slash_command(&command_id) + { + if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status { + let run_commands_in_ranges = invoked_slash_command + .run_commands_in_ranges + .iter() + .cloned() + .collect::>(); + for range in run_commands_in_ranges { + let commands = self.context.update(cx, |context, cx| { + context.reparse(cx); + context + .pending_commands_for_range(range.clone(), cx) + .to_vec() + }); + + for command in commands { + self.run_command( + command.source_range, + &command.name, + &command.arguments, + false, + self.workspace.clone(), + cx, + ); + } + } + } + } + self.editor.update(cx, |editor, cx| { if let Some(invoked_slash_command) = self.context.read(cx).invoked_slash_command(&command_id) diff --git a/crates/assistant/src/context.rs b/crates/assistant/src/context.rs index d6f0a48868..39c31d7c58 100644 --- a/crates/assistant/src/context.rs +++ b/crates/assistant/src/context.rs @@ -381,10 +381,6 @@ pub enum ContextEvent { SlashCommandOutputSectionAdded { section: SlashCommandOutputSection, }, - SlashCommandFinished { - output_range: Range, - run_commands_in_ranges: Vec>, - }, UsePendingTools, ToolFinished { tool_use_id: Arc, @@ -916,6 +912,7 @@ impl Context { InvokedSlashCommand { name: name.into(), range: output_range, + run_commands_in_ranges: Vec::new(), status: InvokedSlashCommandStatus::Running(Task::ready(())), transaction: None, timestamp: id.0, @@ -1914,7 +1911,6 @@ impl Context { } let mut pending_section_stack: Vec = Vec::new(); - let mut run_commands_in_ranges: Vec> = Vec::new(); let mut last_role: Option = None; let mut last_section_range = None; @@ -1980,7 +1976,13 @@ impl Context { let end = this.buffer.read(cx).anchor_before(insert_position); if run_commands_in_text { - run_commands_in_ranges.push(start..end); + if let Some(invoked_slash_command) = + this.invoked_slash_commands.get_mut(&command_id) + { + invoked_slash_command + .run_commands_in_ranges + .push(start..end); + } } } SlashCommandEvent::EndSection => { @@ -2100,6 +2102,7 @@ impl Context { InvokedSlashCommand { name: name.to_string().into(), range: command_range.clone(), + run_commands_in_ranges: Vec::new(), status: InvokedSlashCommandStatus::Running(insert_output_task), transaction: Some(first_transaction), timestamp: command_id.0, @@ -3176,6 +3179,7 @@ pub struct ParsedSlashCommand { pub struct InvokedSlashCommand { pub name: SharedString, pub range: Range, + pub run_commands_in_ranges: Vec>, pub status: InvokedSlashCommandStatus, pub transaction: Option, timestamp: clock::Lamport, diff --git a/crates/assistant/src/slash_command/default_command.rs b/crates/assistant/src/slash_command/default_command.rs index 4d9c9e2ae4..49a7b244e9 100644 --- a/crates/assistant/src/slash_command/default_command.rs +++ b/crates/assistant/src/slash_command/default_command.rs @@ -69,6 +69,10 @@ impl SlashCommand for DefaultSlashCommand { text.push('\n'); } + if !text.ends_with('\n') { + text.push('\n'); + } + Ok(SlashCommandOutput { sections: vec![SlashCommandOutputSection { range: 0..text.len(), From 7853e32f8093ab6195d89a490ff43f0ec62ba0e1 Mon Sep 17 00:00:00 2001 From: Jaagup Averin Date: Tue, 19 Nov 2024 19:53:36 +0200 Subject: [PATCH 054/157] python: Highlight attribute docstrings (#20763) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds more docstring highlights missing from #20486. [PEP257](https://peps.python.org/pep-0257/) defines attribute docstrings as > String literals occurring immediately after a simple assignment at the top level of a module, class, or __init__ method are called “attribute docstrings”. This PR adds `@string.doc` for such cases. Before: ![Screenshot_20241116_162257](https://github.com/user-attachments/assets/6b471cff-717e-4755-9291-d596da927dc6) After: ![Screenshot_20241116_162457](https://github.com/user-attachments/assets/96674157-9c86-45b6-8ce9-e433ca0ae8ea) Release Notes: - Added Python syntax highlighting for attribute docstrings. --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> --- crates/languages/src/python/highlights.scm | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/languages/src/python/highlights.scm b/crates/languages/src/python/highlights.scm index 78e5126d40..6c3f027c19 100644 --- a/crates/languages/src/python/highlights.scm +++ b/crates/languages/src/python/highlights.scm @@ -98,6 +98,25 @@ (parameters)? body: (block (expression_statement (string) @string.doc))) +(module + (expression_statement (assignment)) + . (expression_statement (string) @string.doc)) + +(class_definition + body: (block + (expression_statement (assignment)) + . (expression_statement (string) @string.doc))) + +(class_definition + body: (block + (function_definition + name: (identifier) @function.method.constructor + (#eq? @function.method.constructor "__init__") + body: (block + (expression_statement (assignment)) + . (expression_statement (string) @string.doc))))) + + [ "-" "-=" From 5c6565a9e0d164c5c91f0820aa7da97da23aede3 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 19 Nov 2024 19:49:36 +0100 Subject: [PATCH 055/157] editor: Use completion filter_range for fuzzy matching (#20869) Fixes regression from #13958 Closes #20868 Release Notes: - N/A --- crates/editor/src/editor.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6167c24bff..d303ecf0f3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1036,7 +1036,12 @@ impl CompletionsMenu { let match_candidates = completions .iter() .enumerate() - .map(|(id, completion)| StringMatchCandidate::new(id, completion.label.text.clone())) + .map(|(id, completion)| { + StringMatchCandidate::new( + id, + completion.label.text[completion.label.filter_range.clone()].into(), + ) + }) .collect(); Self { From 496dae968b8fc09118bd60ae7299ab9d44936f16 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 19 Nov 2024 12:25:16 -0700 Subject: [PATCH 056/157] Remove old CPU/Memory events (#20865) Release Notes: - Telemetry: stop reporting CPU/RAM on a timer --- .github/workflows/ci.yml | 18 ++++++++- Cargo.lock | 1 - crates/client/Cargo.toml | 1 - crates/client/src/telemetry.rs | 70 +-------------------------------- crates/collab/src/api/events.rs | 32 ++++----------- 5 files changed, 25 insertions(+), 97 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bed52955ad..ee6f81dd84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -353,7 +353,6 @@ jobs: files: | target/zed-remote-server-linux-x86_64.gz target/release/zed-linux-x86_64.tar.gz - body: "" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -400,6 +399,21 @@ jobs: files: | target/zed-remote-server-linux-aarch64.gz target/release/zed-linux-aarch64.tar.gz - body: "" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + auto-publish-release: + timeout-minutes: 60 + name: Create a Linux bundle + runs-on: + - self-hosted + if: ${{ startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre') }} + needs: [bundle-mac, bundle-linux-aarch64, bundle-linux] + steps: + - name: Upload app bundle to release + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 + with: + draft: false + prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index 38de2f5c00..527190baca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2456,7 +2456,6 @@ dependencies = [ "settings", "sha2", "smol", - "sysinfo", "telemetry_events", "text", "thiserror 1.0.69", diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 9892011297..23716f0c69 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -42,7 +42,6 @@ serde_json.workspace = true settings.workspace = true sha2.workspace = true smol.workspace = true -sysinfo.workspace = true telemetry_events.workspace = true text.workspace = true thiserror.workspace = true diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index b472cf768e..fcb9ced4e5 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -16,11 +16,9 @@ use std::fs::File; use std::io::Write; use std::time::Instant; use std::{env, mem, path::PathBuf, sync::Arc, time::Duration}; -use sysinfo::{CpuRefreshKind, Pid, ProcessRefreshKind, RefreshKind, System}; use telemetry_events::{ - ActionEvent, AppEvent, AssistantEvent, CallEvent, CpuEvent, EditEvent, EditorEvent, Event, - EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, MemoryEvent, ReplEvent, - SettingEvent, + ActionEvent, AppEvent, AssistantEvent, CallEvent, EditEvent, EditorEvent, Event, + EventRequestBody, EventWrapper, ExtensionEvent, InlineCompletionEvent, ReplEvent, SettingEvent, }; use util::{ResultExt, TryFutureExt}; use worktree::{UpdatedEntriesSet, WorktreeId}; @@ -293,48 +291,6 @@ impl Telemetry { state.session_id = Some(session_id); state.app_version = release_channel::AppVersion::global(cx).to_string(); state.os_name = os_name(); - - drop(state); - - let this = self.clone(); - cx.background_executor() - .spawn(async move { - let mut system = System::new_with_specifics( - RefreshKind::new().with_cpu(CpuRefreshKind::everything()), - ); - - let refresh_kind = ProcessRefreshKind::new().with_cpu().with_memory(); - let current_process = Pid::from_u32(std::process::id()); - system.refresh_processes_specifics( - sysinfo::ProcessesToUpdate::Some(&[current_process]), - refresh_kind, - ); - - // Waiting some amount of time before the first query is important to get a reasonable value - // https://docs.rs/sysinfo/0.29.10/sysinfo/trait.ProcessExt.html#tymethod.cpu_usage - const DURATION_BETWEEN_SYSTEM_EVENTS: Duration = Duration::from_secs(4 * 60); - - loop { - smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await; - - let current_process = Pid::from_u32(std::process::id()); - system.refresh_processes_specifics( - sysinfo::ProcessesToUpdate::Some(&[current_process]), - refresh_kind, - ); - let Some(process) = system.process(current_process) else { - log::error!( - "Failed to find own process {current_process:?} in system process table" - ); - // TODO: Fire an error telemetry event - return; - }; - - this.report_memory_event(process.memory(), process.virtual_memory()); - this.report_cpu_event(process.cpu_usage(), system.cpus().len() as u32); - } - }) - .detach(); } pub fn metrics_enabled(self: &Arc) -> bool { @@ -416,28 +372,6 @@ impl Telemetry { self.report_event(event) } - pub fn report_cpu_event(self: &Arc, usage_as_percentage: f32, core_count: u32) { - let event = Event::Cpu(CpuEvent { - usage_as_percentage, - core_count, - }); - - self.report_event(event) - } - - pub fn report_memory_event( - self: &Arc, - memory_in_bytes: u64, - virtual_memory_in_bytes: u64, - ) { - let event = Event::Memory(MemoryEvent { - memory_in_bytes, - virtual_memory_in_bytes, - }); - - self.report_event(event) - } - pub fn report_app_event(self: &Arc, operation: String) -> Event { let event = Event::App(AppEvent { operation }); diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 053657a8d1..80f477df30 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -483,20 +483,7 @@ pub async fn post_events( checksum_matched, )) } - Event::Cpu(event) => to_upload.cpu_events.push(CpuEventRow::from_event( - event.clone(), - wrapper, - &request_body, - first_event_at, - checksum_matched, - )), - Event::Memory(event) => to_upload.memory_events.push(MemoryEventRow::from_event( - event.clone(), - wrapper, - &request_body, - first_event_at, - checksum_matched, - )), + Event::Cpu(_) | Event::Memory(_) => continue, Event::App(event) => to_upload.app_events.push(AppEventRow::from_event( event.clone(), wrapper, @@ -947,6 +934,7 @@ pub struct CpuEventRow { } impl CpuEventRow { + #[allow(unused)] fn from_event( event: CpuEvent, wrapper: &EventWrapper, @@ -1001,6 +989,7 @@ pub struct MemoryEventRow { } impl MemoryEventRow { + #[allow(unused)] fn from_event( event: MemoryEvent, wrapper: &EventWrapper, @@ -1393,7 +1382,7 @@ fn for_snowflake( body: EventRequestBody, first_event_at: chrono::DateTime, ) -> impl Iterator { - body.events.into_iter().map(move |event| { + body.events.into_iter().flat_map(move |event| { let timestamp = first_event_at + Duration::milliseconds(event.milliseconds_since_first_event); let (event_type, mut event_properties) = match &event.event { @@ -1450,14 +1439,7 @@ fn for_snowflake( }, serde_json::to_value(e).unwrap(), ), - Event::Cpu(e) => ( - "System CPU Sampled".to_string(), - serde_json::to_value(e).unwrap(), - ), - Event::Memory(e) => ( - "System Memory Sampled".to_string(), - serde_json::to_value(e).unwrap(), - ), + Event::Cpu(_) | Event::Memory(_) => return None, Event::App(e) => { let mut properties = json!({}); let event_type = match e.operation.trim() { @@ -1577,7 +1559,7 @@ fn for_snowflake( "is_staff": body.is_staff, })); - SnowflakeRow { + Some(SnowflakeRow { time: timestamp, user_id: body.metrics_id.clone(), device_id: body.system_id.clone(), @@ -1585,7 +1567,7 @@ fn for_snowflake( event_properties, user_properties, insert_id: Some(Uuid::new_v4().to_string()), - } + }) }) } From 1c2b3ad782fef847d6083d11252e06732f1d2c42 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Tue, 19 Nov 2024 19:33:35 +0000 Subject: [PATCH 057/157] Add editor::SelectAllMatches to SublimeText base keymap (#20866) `alt-f3` on Linux `ctrl-cmd-g` on MacOS Co-authored-by: Roman Seidelsohn --- assets/keymaps/linux/sublime_text.json | 1 + assets/keymaps/macos/sublime_text.json | 1 + 2 files changed, 2 insertions(+) diff --git a/assets/keymaps/linux/sublime_text.json b/assets/keymaps/linux/sublime_text.json index c4bffb56b0..57ef4b876b 100644 --- a/assets/keymaps/linux/sublime_text.json +++ b/assets/keymaps/linux/sublime_text.json @@ -16,6 +16,7 @@ "ctrl-shift-l": "editor::SplitSelectionIntoLines", "ctrl-shift-a": "editor::SelectLargerSyntaxNode", "ctrl-shift-d": "editor::DuplicateLineDown", + "alt-f3": "editor::SelectAllMatches", // find_all_under "f12": "editor::GoToDefinition", "ctrl-f12": "editor::GoToDefinitionSplit", "shift-f12": "editor::FindAllReferences", diff --git a/assets/keymaps/macos/sublime_text.json b/assets/keymaps/macos/sublime_text.json index dd57386424..f4c09b5144 100644 --- a/assets/keymaps/macos/sublime_text.json +++ b/assets/keymaps/macos/sublime_text.json @@ -19,6 +19,7 @@ "cmd-shift-l": "editor::SplitSelectionIntoLines", "cmd-shift-a": "editor::SelectLargerSyntaxNode", "cmd-shift-d": "editor::DuplicateLineDown", + "ctrl-cmd-g": "editor::SelectAllMatches", // find_all_under "shift-f12": "editor::FindAllReferences", "alt-cmd-down": "editor::GoToDefinition", "ctrl-alt-cmd-down": "editor::GoToDefinitionSplit", From ea5131ce0a9d753e81969d6f55efb30882eb5c7c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 19 Nov 2024 12:52:00 -0700 Subject: [PATCH 058/157] Country Code To Snowflake (#20875) Release Notes: - N/A --------- Co-authored-by: Nathan Sobo --- crates/collab/src/api/events.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 80f477df30..57ac43ca56 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -418,7 +418,7 @@ pub async fn post_events( if let Some(kinesis_client) = app.kinesis_client.clone() { if let Some(stream) = app.config.kinesis_stream.clone() { let mut request = kinesis_client.put_records().stream_name(stream); - for row in for_snowflake(request_body.clone(), first_event_at) { + for row in for_snowflake(request_body.clone(), first_event_at, country_code.clone()) { if let Some(data) = serde_json::to_vec(&row).log_err() { request = request.records( aws_sdk_kinesis::types::PutRecordsRequestEntry::builder() @@ -1381,6 +1381,7 @@ pub fn calculate_json_checksum(app: Arc, json: &impl AsRef<[u8]>) -> O fn for_snowflake( body: EventRequestBody, first_event_at: chrono::DateTime, + country_code: Option, ) -> impl Iterator { body.events.into_iter().flat_map(move |event| { let timestamp = @@ -1553,6 +1554,9 @@ fn for_snowflake( body.release_channel.clone().into(), ); map.insert("signed_in".to_string(), event.signed_in.into()); + if let Some(country_code) = country_code.as_ref() { + map.insert("country_code".to_string(), country_code.clone().into()); + } } let user_properties = Some(serde_json::json!({ From f77b6ab79c6fc8619b7853934519ce1c19c6b3fd Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 19 Nov 2024 13:43:24 -0700 Subject: [PATCH 059/157] Fix space repeating in terminal (#20877) This is broken because of the way we try to emulate macOS's ApplePressAndHoldEnabled. Release Notes: - Fixed holding down space in the terminal (preview only) --- crates/gpui/src/platform/mac/events.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/events.rs b/crates/gpui/src/platform/mac/events.rs index aeff08ada8..51716cccb4 100644 --- a/crates/gpui/src/platform/mac/events.rs +++ b/crates/gpui/src/platform/mac/events.rs @@ -260,7 +260,10 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { #[allow(non_upper_case_globals)] let key = match first_char { - Some(SPACE_KEY) => "space".to_string(), + Some(SPACE_KEY) => { + ime_key = Some(" ".to_string()); + "space".to_string() + } Some(BACKSPACE_KEY) => "backspace".to_string(), Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter".to_string(), Some(ESCAPE_KEY) => "escape".to_string(), From 705a06c3dd62cd85613092163575bc7cf10b9d30 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 19 Nov 2024 16:38:14 -0700 Subject: [PATCH 060/157] Send Country/OS/Version amplitude style (#20884) Release Notes: - N/A --- crates/collab/src/api/events.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 57ac43ca56..1c936bac39 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -1561,6 +1561,9 @@ fn for_snowflake( let user_properties = Some(serde_json::json!({ "is_staff": body.is_staff, + "Country": country_code.clone(), + "OS": format!("{} {}", body.os_name, body.os_version.clone().unwrap_or_default()), + "Version": body.app_version.clone(), })); Some(SnowflakeRow { From c2668bc953c6675e7d7c31014f045b28aff6f99a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 19 Nov 2024 19:08:33 -0700 Subject: [PATCH 061/157] Fix draft-releaase-notes (#20885) Turns out this was broken because (a) we didn't have tags fetched, and (b) because the gh-release action we use is buggy. Release Notes: - N/A --- .github/workflows/ci.yml | 21 ++++----------------- script/create-draft-release | 8 ++++++++ 2 files changed, 12 insertions(+), 17 deletions(-) create mode 100755 script/create-draft-release diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee6f81dd84..f22a8a518e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -244,6 +244,7 @@ jobs: # # 25 was chosen arbitrarily. fetch-depth: 25 + fetch-tags: true clean: false - name: Limit target directory size @@ -261,6 +262,9 @@ jobs: mkdir -p target/ # Ignore any errors that occur while drafting release notes to not fail the build. script/draft-release-notes "$RELEASE_VERSION" "$RELEASE_CHANNEL" > target/release-notes.md || true + script/create-draft-release target/release-notes.md + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Generate license file run: script/generate-licenses @@ -306,7 +310,6 @@ jobs: target/aarch64-apple-darwin/release/Zed-aarch64.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg target/release/Zed.dmg - body_path: target/release-notes.md env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -401,19 +404,3 @@ jobs: target/release/zed-linux-aarch64.tar.gz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - auto-publish-release: - timeout-minutes: 60 - name: Create a Linux bundle - runs-on: - - self-hosted - if: ${{ startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre') }} - needs: [bundle-mac, bundle-linux-aarch64, bundle-linux] - steps: - - name: Upload app bundle to release - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1 - with: - draft: false - prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/script/create-draft-release b/script/create-draft-release new file mode 100755 index 0000000000..e72c6d141c --- /dev/null +++ b/script/create-draft-release @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +preview="" +if [[ "$GITHUB_REF_NAME" == *"-pre" ]]; then + preview="-p" +fi + +gh release create -d "$GITHUB_REF_NAME" -F "$1" $preview From ad6a07e57426a8ef85e3a488f15130fc9b279204 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 19 Nov 2024 20:00:03 -0700 Subject: [PATCH 062/157] Remove comments from discord release announcements (#20888) Release Notes: - N/A --- script/draft-release-notes | 4 ---- 1 file changed, 4 deletions(-) diff --git a/script/draft-release-notes b/script/draft-release-notes index 287997ff79..eeb53bbb22 100755 --- a/script/draft-release-notes +++ b/script/draft-release-notes @@ -64,10 +64,6 @@ async function main() { } console.log(releaseNotes.join("\n") + "\n"); - console.log(""); } function getCommits(oldTag, newTag) { From 3c57a4071cd3e3400a1e0b3329fccb0f0477e1ea Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 19 Nov 2024 20:00:11 -0700 Subject: [PATCH 063/157] vim: Fix jj to exit insert mode (#20890) Release Notes: - (Preview only) fixed binding `jj` to exit insert mode --- crates/gpui/src/window.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 9a028c1f01..ec1fd601ec 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3038,7 +3038,7 @@ impl<'a> WindowContext<'a> { return true; } - if let Some(input) = keystroke.ime_key { + if let Some(input) = keystroke.with_simulated_ime().ime_key { if let Some(mut input_handler) = self.window.platform_window.take_input_handler() { input_handler.dispatch_input(&input, self); self.window.platform_window.set_input_handler(input_handler); @@ -3482,7 +3482,13 @@ impl<'a> WindowContext<'a> { if !self.propagate_event { continue 'replay; } - if let Some(input) = replay.keystroke.ime_key.as_ref().cloned() { + if let Some(input) = replay + .keystroke + .with_simulated_ime() + .ime_key + .as_ref() + .cloned() + { if let Some(mut input_handler) = self.window.platform_window.take_input_handler() { input_handler.dispatch_input(&input, self); self.window.platform_window.set_input_handler(input_handler) From e03968f53832ac6d42cc338f57f20035a848652f Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:22:07 +0100 Subject: [PATCH 064/157] pane: Fix panic when dragging non-pinned item onto it's pinned copy in another pane (#20900) Closes #20889 Release Notes: - N/A --- crates/workspace/src/pane.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 22d06ec21a..e9b81d4554 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2455,6 +2455,8 @@ impl Pane { to_pane = workspace.split_pane(to_pane, split_direction, cx); } let old_ix = from_pane.read(cx).index_for_item_id(item_id); + let old_len = to_pane.read(cx).items.len(); + move_item(&from_pane, &to_pane, item_id, ix, cx); if to_pane == from_pane { if let Some(old_index) = old_ix { to_pane.update(cx, |this, _| { @@ -2472,7 +2474,10 @@ impl Pane { } } else { to_pane.update(cx, |this, _| { - if this.has_pinned_tabs() && ix < this.pinned_tab_count { + if this.items.len() > old_len // Did we not deduplicate on drag? + && this.has_pinned_tabs() + && ix < this.pinned_tab_count + { this.pinned_tab_count += 1; } }); @@ -2484,7 +2489,6 @@ impl Pane { } }) } - move_item(&from_pane, &to_pane, item_id, ix, cx); }); }) .log_err(); From 743165fa6c5c46d2dab907bc0122f18e1465b7b2 Mon Sep 17 00:00:00 2001 From: Thorsten Ball Date: Wed, 20 Nov 2024 14:38:56 +0100 Subject: [PATCH 065/157] Fix assistant hints showing up when selecting \n in Vim mode (#20899) We also need to check whether the selection is empty, not just whether its head is on an empty line. Release Notes: - N/A Co-authored-by: Antonio --- crates/editor/src/editor.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d303ecf0f3..7f31cdedd3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -11875,7 +11875,15 @@ impl Editor { style: &EditorStyle, cx: &mut WindowContext, ) -> Option { - if !self.newest_selection_head_on_empty_line(cx) || self.has_active_inline_completion(cx) { + let selection = self.selections.newest::(cx); + if !selection.is_empty() { + return None; + }; + + let snapshot = self.buffer.read(cx).snapshot(cx); + let buffer_row = MultiBufferRow(selection.head().row); + + if snapshot.line_len(buffer_row) != 0 || self.has_active_inline_completion(cx) { return None; } From b63394f4bd1037faf4f3d43e2119d1415232c595 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 20 Nov 2024 10:45:44 -0500 Subject: [PATCH 066/157] v0.164.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 527190baca..d65fa24b4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15422,7 +15422,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.163.0" +version = "0.164.0" dependencies = [ "activity_indicator", "anyhow", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index e5d4cb7623..6f511c2951 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -2,7 +2,7 @@ description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.163.0" +version = "0.164.0" publish = false license = "GPL-3.0-or-later" authors = ["Zed Team "] From 973498e075999b295cf5fd3910be9fbc34a77976 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 20 Nov 2024 10:53:51 -0500 Subject: [PATCH 067/157] context_servers: Make `settings` field show up in settings completions (#20905) This PR fixes an issue where the `settings` field for a context server would not show up in the completions when editing the Zed settings. It seems that `schemars` doesn't like the `serde_json::Value` as a setting type when generating the JSON Schema. To address this, we are using a custom schema of an empty object (as we don't yet have any other information as to the structure of a given context server's settings). Release Notes: - context_servers: Fixed `settings` field not being suggested in completions when editing `settings.json`. --- crates/context_servers/src/manager.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/context_servers/src/manager.rs b/crates/context_servers/src/manager.rs index fc0c77e821..9b9520e223 100644 --- a/crates/context_servers/src/manager.rs +++ b/crates/context_servers/src/manager.rs @@ -24,6 +24,8 @@ use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, Subscription, Tas use log; use parking_lot::RwLock; use project::Project; +use schemars::gen::SchemaGenerator; +use schemars::schema::{InstanceType, Schema, SchemaObject}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources, SettingsStore}; @@ -43,9 +45,17 @@ pub struct ContextServerSettings { #[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)] pub struct ServerConfig { pub command: Option, + #[schemars(schema_with = "server_config_settings_json_schema")] pub settings: Option, } +fn server_config_settings_json_schema(_generator: &mut SchemaGenerator) -> Schema { + Schema::Object(SchemaObject { + instance_type: Some(InstanceType::Object.into()), + ..Default::default() + }) +} + #[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)] pub struct ServerCommand { pub path: String, From 41fd9189e33b966228020417ffb53fea85435e05 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 20 Nov 2024 11:30:14 -0500 Subject: [PATCH 068/157] context_servers: Document settings (#20907) This PR documents the settings type for context servers so that the documentation shows up when editing the `settings.json` file. Release Notes: - N/A --- crates/context_servers/src/manager.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/context_servers/src/manager.rs b/crates/context_servers/src/manager.rs index 9b9520e223..c95fcd239d 100644 --- a/crates/context_servers/src/manager.rs +++ b/crates/context_servers/src/manager.rs @@ -38,13 +38,21 @@ use crate::{ #[derive(Deserialize, Serialize, Default, Clone, PartialEq, Eq, JsonSchema, Debug)] pub struct ContextServerSettings { + /// Settings for context servers used in the Assistant. #[serde(default)] pub context_servers: HashMap, ServerConfig>, } #[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)] pub struct ServerConfig { + /// The command to run this context server. + /// + /// This will override the command set by an extension. pub command: Option, + /// The settings for this context server. + /// + /// Consult the documentation for the context server to see what settings + /// are supported. #[schemars(schema_with = "server_config_settings_json_schema")] pub settings: Option, } From 1475a7000f790e9133263182afca4fa3a93f5ed4 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 20 Nov 2024 10:27:50 -0700 Subject: [PATCH 069/157] Don't re-render the menu so often (#20914) Closes #20710 Release Notes: - Fixes opening the menu when Chinese Pinyin keyboard is in use --- crates/zed/src/zed.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 0f10f1914b..867ffa91e6 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -824,8 +824,13 @@ pub fn handle_keymap_file_changes( }) .detach(); - cx.on_keyboard_layout_change(move |_| { - keyboard_layout_tx.unbounded_send(()).ok(); + let mut current_mapping = settings::get_key_equivalents(cx.keyboard_layout()); + cx.on_keyboard_layout_change(move |cx| { + let next_mapping = settings::get_key_equivalents(cx.keyboard_layout()); + if next_mapping != current_mapping { + current_mapping = next_mapping; + keyboard_layout_tx.unbounded_send(()).ok(); + } }) .detach(); From 7e67753d51bc2f7db294f3de46380b28ec0e4e7d Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Wed, 20 Nov 2024 18:32:05 +0000 Subject: [PATCH 070/157] ci: Fix for checkout action with fetch-tags (#20917) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f22a8a518e..43af9309fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -244,8 +244,8 @@ jobs: # # 25 was chosen arbitrarily. fetch-depth: 25 - fetch-tags: true clean: false + ref: ${{ github.ref }} - name: Limit target directory size run: script/clear-target-dir-if-larger-than 100 From 8c342ef706708aa141dda25e73c9d129201a6aeb Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Wed, 20 Nov 2024 18:35:00 +0000 Subject: [PATCH 071/157] Bump JSON schemas: package.json, tsconfig.json (#20910) Add script/update-json-schemas Updated JSON schemas to [SchemaStore/schemastore@569a343](https://github.com/SchemaStore/schemastore/tree/569a343137332470676617964bf332e06c1812eb) (2024-11-19) --- .../languages/src/json/schemas/package.json | 11 ++-- .../languages/src/json/schemas/tsconfig.json | 62 ++++++++++++++----- script/update-json-schemas | 25 ++++++++ 3 files changed, 79 insertions(+), 19 deletions(-) create mode 100755 script/update-json-schemas diff --git a/crates/languages/src/json/schemas/package.json b/crates/languages/src/json/schemas/package.json index 42c8f3c114..79d2457276 100644 --- a/crates/languages/src/json/schemas/package.json +++ b/crates/languages/src/json/schemas/package.json @@ -139,7 +139,7 @@ } }, "patternProperties": { - "^(?![\\.0-9]).": { + "^[^.0-9]+$": { "$ref": "#/definitions/packageExportsEntryOrFallback", "description": "The module path that is resolved when this environment matches the property name." } @@ -616,7 +616,7 @@ } } }, - "bundledDependencies": { + "bundleDependencies": { "description": "Array of package names that will be bundled when publishing the package.", "oneOf": [ { @@ -630,8 +630,8 @@ } ] }, - "bundleDependencies": { - "description": "DEPRECATED: This field is honored, but \"bundledDependencies\" is the correct field name.", + "bundledDependencies": { + "description": "DEPRECATED: This field is honored, but \"bundleDependencies\" is the correct field name.", "oneOf": [ { "type": "array", @@ -734,6 +734,9 @@ "registry": { "type": "string", "format": "uri" + }, + "provenance": { + "type": "boolean" } }, "additionalProperties": true diff --git a/crates/languages/src/json/schemas/tsconfig.json b/crates/languages/src/json/schemas/tsconfig.json index 808fc6f966..9174a58537 100644 --- a/crates/languages/src/json/schemas/tsconfig.json +++ b/crates/languages/src/json/schemas/tsconfig.json @@ -232,7 +232,7 @@ "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", "description": "Enable importing files with any extension, provided a declaration file is present.", "type": ["boolean", "null"], - "markdownDescription": "Enable importing files with any extension, provided a declaration file is present.\n\nSee more: https://www.typescriptlang.org/tsconfig#allowImportingTsExtensions" + "markdownDescription": "Enable importing files with any extension, provided a declaration file is present.\n\nSee more: https://www.typescriptlang.org/tsconfig#allowArbitraryExtensions" }, "allowImportingTsExtensions": { "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", @@ -426,17 +426,17 @@ "anyOf": [ { "enum": [ - "Classic", - "Node", - "Node10", - "Node16", - "NodeNext", - "Bundler" + "classic", + "node", + "node10", + "node16", + "nodenext", + "bundler" ], "markdownEnumDescriptions": [ - "It’s recommended to use `\"Node16\"` instead", - "Deprecated, use `\"Node10\"` in TypeScript 5.0+ instead", - "It’s recommended to use `\"Node16\"` instead", + "It’s recommended to use `\"node16\"` instead", + "Deprecated, use `\"node10\"` in TypeScript 5.0+ instead", + "It’s recommended to use `\"node16\"` instead", "This is the recommended setting for libraries and Node.js applications", "This is the recommended setting for libraries and Node.js applications", "This is the recommended setting in TypeScript 5.0+ for applications that use a bundler" @@ -497,10 +497,10 @@ }, "noUnusedLocals": { "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", - "description": "Enable error reporting when a local variables aren't read.", + "description": "Enable error reporting when a local variable isn't read.", "type": ["boolean", "null"], "default": false, - "markdownDescription": "Enable error reporting when a local variables aren't read.\n\nSee more: https://www.typescriptlang.org/tsconfig#noUnusedLocals" + "markdownDescription": "Enable error reporting when a local variable isn't read.\n\nSee more: https://www.typescriptlang.org/tsconfig#noUnusedLocals" }, "noUnusedParameters": { "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", @@ -949,14 +949,19 @@ "ESNext.Array", "ESNext.AsyncIterable", "ESNext.BigInt", + "ESNext.Collection", "ESNext.Intl", + "ESNext.Object", "ESNext.Promise", + "ESNext.Regexp", "ESNext.String", "ESNext.Symbol", "DOM", + "DOM.AsyncIterable", "DOM.Iterable", "ScriptHost", "WebWorker", + "WebWorker.AsyncIterable", "WebWorker.ImportScripts", "Webworker.Iterable", "ES7", @@ -1022,13 +1027,13 @@ "pattern": "^[Ee][Ss][Nn][Ee][Xx][Tt](\\.([Aa][Rr][Rr][Aa][Yy]|[Aa][Ss][Yy][Nn][Cc][Ii][Tt][Ee][Rr][Aa][Bb][Ll][Ee]|[Bb][Ii][Gg][Ii][Nn][Tt]|[Ii][Nn][Tt][Ll]|[Pp][Rr][Oo][Mm][Ii][Ss][Ee]|[Ss][Tt][Rr][Ii][Nn][Gg]|[Ss][Yy][Mm][Bb][Oo][Ll]|[Ww][Ee][Aa][Kk][Rr][Ee][Ff]|[Dd][Ee][Cc][Oo][Rr][Aa][Tt][Oo][Rr][Ss]|[Dd][Ii][Ss][Pp][Oo][Ss][Aa][Bb][Ll][Ee]))?$" }, { - "pattern": "^[Dd][Oo][Mm](\\.[Ii][Tt][Ee][Rr][Aa][Bb][Ll][Ee])?$" + "pattern": "^[Dd][Oo][Mm](\\.([Aa][Ss][Yy][Nn][Cc])?[Ii][Tt][Ee][Rr][Aa][Bb][Ll][Ee])?$" }, { "pattern": "^[Ss][Cc][Rr][Ii][Pp][Tt][Hh][Oo][Ss][Tt]$" }, { - "pattern": "^[Ww][Ee][Bb][Ww][Oo][Rr][Kk][Ee][Rr](\\.([Ii][Mm][Pp][Oo][Rr][Tt][Ss][Cc][Rr][Ii][Pp][Tt][Ss]|[Ii][Tt][Ee][Rr][Aa][Bb][Ll][Ee]))?$" + "pattern": "^[Ww][Ee][Bb][Ww][Oo][Rr][Kk][Ee][Rr](\\.([Ii][Mm][Pp][Oo][Rr][Tt][Ss][Cc][Rr][Ii][Pp][Tt][Ss]|([Aa][Ss][Yy][Nn][Cc])?[Ii][Tt][Ee][Rr][Aa][Bb][Ll][Ee]))?$" }, { "pattern": "^[Dd][Ee][Cc][Oo][Rr][Aa][Tt][Oo][Rr][Ss](\\.([Ll][Ee][Gg][Aa][Cc][Yy]))?$" @@ -1203,6 +1208,34 @@ "description": "Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting.", "type": ["boolean", "null"], "markdownDescription": "Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting.\n\nSee more: https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax" + }, + "noCheck": { + "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", + "description": "Disable full type checking (only critical parse and emit errors will be reported)", + "type": ["boolean", "null"], + "default": false, + "markdownDescription": "Disable full type checking (only critical parse and emit errors will be reported)\n\nSee more: https://www.typescriptlang.org/tsconfig#noCheck" + }, + "isolatedDeclarations": { + "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", + "description": "Require sufficient annotation on exports so other tools can trivially generate declaration files.", + "type": ["boolean", "null"], + "default": false, + "markdownDescription": "Require sufficient annotation on exports so other tools can trivially generate declaration files.\n\nSee more: https://www.typescriptlang.org/tsconfig#isolatedDeclarations" + }, + "noUncheckedSideEffectImports": { + "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", + "description": "Check side effect imports.", + "type": ["boolean", "null"], + "default": false, + "markdownDescription": "Check side effect imports.\n\nSee more: https://www.typescriptlang.org/tsconfig#noUncheckedSideEffectImports" + }, + "strictBuiltinIteratorReturn": { + "$comment": "The value of 'null' is UNDOCUMENTED (https://github.com/microsoft/TypeScript/pull/18058).", + "description": "Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'.", + "type": ["boolean", "null"], + "default": false, + "markdownDescription": "Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'.\n\nSee more: https://www.typescriptlang.org/tsconfig#strictBuiltinIteratorReturn" } } } @@ -1423,4 +1456,3 @@ "title": "JSON schema for the TypeScript compiler's configuration file", "type": "object" } - diff --git a/script/update-json-schemas b/script/update-json-schemas new file mode 100755 index 0000000000..182e0ff03b --- /dev/null +++ b/script/update-json-schemas @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +set -euo pipefail + +cd "$(dirname "$0")/.." || exit 1 +cd crates/languages/src/json/schemas +files=( + "tsconfig.json" + "package.json" +) +for file in "${files[@]}"; do + curl -sL -o "$file" "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/$file" +done + +HASH="$(curl -s 'https://api.github.com/repos/SchemaStore/schemastore/commits/HEAD' | jq -r '.sha')" +SHORT_HASH="${HASH:0:7}" +DATE="$(curl -s 'https://api.github.com/repos/SchemaStore/schemastore/commits/HEAD' |jq -r .commit.author.date | cut -c1-10)" +echo +echo "Updated JSON schemas to [SchemaStore/schemastore@$SHORT_HASH](https://github.com/SchemaStore/schemastore/tree/$HASH) ($DATE)" +echo +for file in "${files[@]}"; do + echo "- [$file](https://github.com/SchemaStore/schemastore/commits/master/src/schemas/json/$file)" \ + "@ [$SHORT_HASH](https://raw.githubusercontent.com/SchemaStore/schemastore/$HASH/src/schemas/json/$file)" +done +echo From e0761db62dda8f4ed3ceb78a496ca9a6f3eaed6f Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 20 Nov 2024 13:46:44 -0700 Subject: [PATCH 072/157] Revert: "a" for "vim::AngleBrackets" (#20918) The replacement "g" didn't seem to work for everyone. Closes #20912 Updates #20104 Release Notes: - vim: Restores `dia` to mean "delete in argument" instead of "delete within angle brackets". To keep this in your own keymap use: ``` { "context": "vim_operator == a || vim_operator == i || vim_operator == cs", "use_layout_keys": true, "bindings": { "a": "vim::AngleBrackets" } } ``` --- assets/keymaps/vim.json | 3 +-- crates/vim/src/object.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 83e332a3f4..10b2009511 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -381,8 +381,7 @@ "shift-b": "vim::CurlyBrackets", "<": "vim::AngleBrackets", ">": "vim::AngleBrackets", - "a": "vim::AngleBrackets", - "g": "vim::Argument" + "a": "vim::Argument" } }, { diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 7c1f2fdb4c..f97312e7f8 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -1407,7 +1407,7 @@ mod test { // Generic arguments cx.set_state("fn boop() {}", Mode::Normal); - cx.simulate_keystrokes("v i g"); + cx.simulate_keystrokes("v i a"); cx.assert_state("fn boop<«A: Debugˇ», B>() {}", Mode::Visual); // Function arguments @@ -1415,11 +1415,11 @@ mod test { "fn boop(ˇarg_a: (Tuple, Of, Types), arg_b: String) {}", Mode::Normal, ); - cx.simulate_keystrokes("d a g"); + cx.simulate_keystrokes("d a a"); cx.assert_state("fn boop(ˇarg_b: String) {}", Mode::Normal); cx.set_state("std::namespace::test(\"strinˇg\", a.b.c())", Mode::Normal); - cx.simulate_keystrokes("v a g"); + cx.simulate_keystrokes("v a a"); cx.assert_state("std::namespace::test(«\"string\", ˇ»a.b.c())", Mode::Visual); // Tuple, vec, and array arguments @@ -1427,34 +1427,34 @@ mod test { "fn boop(arg_a: (Tuple, Ofˇ, Types), arg_b: String) {}", Mode::Normal, ); - cx.simulate_keystrokes("c i g"); + cx.simulate_keystrokes("c i a"); cx.assert_state( "fn boop(arg_a: (Tuple, ˇ, Types), arg_b: String) {}", Mode::Insert, ); cx.set_state("let a = (test::call(), 'p', my_macro!{ˇ});", Mode::Normal); - cx.simulate_keystrokes("c a g"); + cx.simulate_keystrokes("c a a"); cx.assert_state("let a = (test::call(), 'p'ˇ);", Mode::Insert); cx.set_state("let a = [test::call(ˇ), 300];", Mode::Normal); - cx.simulate_keystrokes("c i g"); + cx.simulate_keystrokes("c i a"); cx.assert_state("let a = [ˇ, 300];", Mode::Insert); cx.set_state( "let a = vec![Vec::new(), vecˇ![test::call(), 300]];", Mode::Normal, ); - cx.simulate_keystrokes("c a g"); + cx.simulate_keystrokes("c a a"); cx.assert_state("let a = vec![Vec::new()ˇ];", Mode::Insert); // Cursor immediately before / after brackets cx.set_state("let a = [test::call(first_arg)ˇ]", Mode::Normal); - cx.simulate_keystrokes("v i g"); + cx.simulate_keystrokes("v i a"); cx.assert_state("let a = [«test::call(first_arg)ˇ»]", Mode::Visual); cx.set_state("let a = [test::callˇ(first_arg)]", Mode::Normal); - cx.simulate_keystrokes("v i g"); + cx.simulate_keystrokes("v i a"); cx.assert_state("let a = [«test::call(first_arg)ˇ»]", Mode::Visual); } From e31f44450e5e8ba77250b27a81ee733ad14ddd81 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 20 Nov 2024 16:05:43 -0500 Subject: [PATCH 073/157] title_bar: Remove dependency on `extensions_ui` (#20929) This PR removes a dependency on the `extensions_ui` from the `title_bar` crate. This dependency only existed to reference the `Extensions` action, which has now been moved to the `zed_actions` crate. This allows `title_bar` to move up in the crate dependency graph. Release Notes: - N/A --- Cargo.lock | 3 +-- crates/extensions_ui/Cargo.toml | 1 + crates/extensions_ui/src/extensions_ui.rs | 4 ++-- crates/title_bar/Cargo.toml | 1 - crates/title_bar/src/title_bar.rs | 4 ++-- crates/welcome/Cargo.toml | 1 - crates/welcome/src/welcome.rs | 2 +- crates/zed/src/zed/app_menus.rs | 2 +- crates/zed_actions/src/lib.rs | 1 + 9 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d65fa24b4a..bb2fb86dad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4218,6 +4218,7 @@ dependencies = [ "vim", "wasmtime-wasi", "workspace", + "zed_actions", ] [[package]] @@ -12575,7 +12576,6 @@ dependencies = [ "collections", "command_palette", "editor", - "extensions_ui", "feature_flags", "feedback", "gpui", @@ -14358,7 +14358,6 @@ dependencies = [ "client", "db", "editor", - "extensions_ui", "fuzzy", "gpui", "inline_completion_button", diff --git a/crates/extensions_ui/Cargo.toml b/crates/extensions_ui/Cargo.toml index 9709aa7a2b..2ff2f21696 100644 --- a/crates/extensions_ui/Cargo.toml +++ b/crates/extensions_ui/Cargo.toml @@ -44,6 +44,7 @@ util.workspace = true vim.workspace = true wasmtime-wasi.workspace = true workspace.workspace = true +zed_actions.workspace = true [dev-dependencies] editor = { workspace = true, features = ["test-support"] } diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index c2ef9cf9e6..01e2b1dd66 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -38,12 +38,12 @@ use crate::extension_version_selector::{ ExtensionVersionSelector, ExtensionVersionSelectorDelegate, }; -actions!(zed, [Extensions, InstallDevExtension]); +actions!(zed, [InstallDevExtension]); pub fn init(cx: &mut AppContext) { cx.observe_new_views(move |workspace: &mut Workspace, cx| { workspace - .register_action(move |workspace, _: &Extensions, cx| { + .register_action(move |workspace, _: &zed_actions::Extensions, cx| { let existing = workspace .active_pane() .read(cx) diff --git a/crates/title_bar/Cargo.toml b/crates/title_bar/Cargo.toml index df991613ae..569231bb9c 100644 --- a/crates/title_bar/Cargo.toml +++ b/crates/title_bar/Cargo.toml @@ -32,7 +32,6 @@ auto_update.workspace = true call.workspace = true client.workspace = true command_palette.workspace = true -extensions_ui.workspace = true feedback.workspace = true feature_flags.workspace = true gpui.workspace = true diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 2ea9ddafd7..44301520ac 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -581,7 +581,7 @@ impl TitleBar { .action("Settings", zed_actions::OpenSettings.boxed_clone()) .action("Key Bindings", Box::new(zed_actions::OpenKeymap)) .action("Themes…", theme_selector::Toggle::default().boxed_clone()) - .action("Extensions", extensions_ui::Extensions.boxed_clone()) + .action("Extensions", zed_actions::Extensions.boxed_clone()) .separator() .link( "Book Onboarding", @@ -617,7 +617,7 @@ impl TitleBar { menu.action("Settings", zed_actions::OpenSettings.boxed_clone()) .action("Key Bindings", Box::new(zed_actions::OpenKeymap)) .action("Themes…", theme_selector::Toggle::default().boxed_clone()) - .action("Extensions", extensions_ui::Extensions.boxed_clone()) + .action("Extensions", zed_actions::Extensions.boxed_clone()) .separator() .link( "Book Onboarding", diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index 0db1af9252..30645d5f12 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -18,7 +18,6 @@ test-support = [] anyhow.workspace = true client.workspace = true db.workspace = true -extensions_ui.workspace = true fuzzy.workspace = true gpui.workspace = true inline_completion_button.workspace = true diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index c8d5bf6dfc..02ce0750c4 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -250,7 +250,7 @@ impl Render for WelcomePage { "welcome page: open extensions".to_string(), ); cx.dispatch_action(Box::new( - extensions_ui::Extensions, + zed_actions::Extensions, )); })), ) diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index 5c01724ba7..09e21f20ab 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -32,7 +32,7 @@ pub fn app_menus() -> Vec { items: vec![], }), MenuItem::separator(), - MenuItem::action("Extensions", extensions_ui::Extensions), + MenuItem::action("Extensions", zed_actions::Extensions), MenuItem::action("Install CLI", install_cli::Install), MenuItem::separator(), MenuItem::action("Hide Zed", super::Hide), diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 7ea5c923c2..bbe774652e 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -32,6 +32,7 @@ actions!( Quit, OpenKeymap, About, + Extensions, OpenLicenses, OpenTelemetryLog, DecreaseBufferFontSize, From e076f55d7827edff28196a15705329c49f828a91 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 20 Nov 2024 16:19:20 -0500 Subject: [PATCH 074/157] language_model: Remove dependency on `inline_completion_button` (#20930) This PR removes a dependency on the `inline_completion_button` crate from the `language_model` crate. We were taking on this dependency solely to call `initiate_sign_in`, which can easily be moved to the `copilot` crate. This allows `language_model` to move up in the crate dependency graph. Release Notes: - N/A --- Cargo.lock | 4 +- crates/copilot/src/copilot.rs | 4 +- crates/copilot/src/sign_in.rs | 71 +++++++++++++++++- crates/inline_completion_button/Cargo.toml | 1 - .../src/inline_completion_button.rs | 72 +------------------ crates/language_model/Cargo.toml | 1 - .../src/provider/copilot_chat.rs | 4 +- crates/welcome/Cargo.toml | 2 +- crates/welcome/src/welcome.rs | 2 +- 9 files changed, 78 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb2fb86dad..c0f9fd746f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6076,7 +6076,6 @@ dependencies = [ "supermaven", "theme", "ui", - "util", "workspace", "zed_actions", ] @@ -6521,7 +6520,6 @@ dependencies = [ "gpui", "http_client", "image", - "inline_completion_button", "language", "log", "menu", @@ -14356,11 +14354,11 @@ version = "0.1.0" dependencies = [ "anyhow", "client", + "copilot", "db", "editor", "fuzzy", "gpui", - "inline_completion_button", "install_cli", "picker", "project", diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index b654df1d6e..7ea289706c 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -38,8 +38,8 @@ use std::{ }; use util::{fs::remove_matching, maybe, ResultExt}; -pub use copilot_completion_provider::CopilotCompletionProvider; -pub use sign_in::CopilotCodeVerification; +pub use crate::copilot_completion_provider::CopilotCompletionProvider; +pub use crate::sign_in::{initiate_sign_in, CopilotCodeVerification}; actions!( copilot, diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index d63710983b..68f0eed577 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -5,10 +5,79 @@ use gpui::{ Styled, Subscription, ViewContext, }; use ui::{prelude::*, Button, Label, Vector, VectorName}; -use workspace::ModalView; +use util::ResultExt as _; +use workspace::notifications::NotificationId; +use workspace::{ModalView, Toast, Workspace}; const COPILOT_SIGN_UP_URL: &str = "https://github.com/features/copilot"; +struct CopilotStartingToast; + +pub fn initiate_sign_in(cx: &mut WindowContext) { + let Some(copilot) = Copilot::global(cx) else { + return; + }; + let status = copilot.read(cx).status(); + let Some(workspace) = cx.window_handle().downcast::() else { + return; + }; + match status { + Status::Starting { task } => { + let Some(workspace) = cx.window_handle().downcast::() else { + return; + }; + + let Ok(workspace) = workspace.update(cx, |workspace, cx| { + workspace.show_toast( + Toast::new( + NotificationId::unique::(), + "Copilot is starting...", + ), + cx, + ); + workspace.weak_handle() + }) else { + return; + }; + + cx.spawn(|mut cx| async move { + task.await; + if let Some(copilot) = cx.update(|cx| Copilot::global(cx)).ok().flatten() { + workspace + .update(&mut cx, |workspace, cx| match copilot.read(cx).status() { + Status::Authorized => workspace.show_toast( + Toast::new( + NotificationId::unique::(), + "Copilot has started!", + ), + cx, + ), + _ => { + workspace.dismiss_toast( + &NotificationId::unique::(), + cx, + ); + copilot + .update(cx, |copilot, cx| copilot.sign_in(cx)) + .detach_and_log_err(cx); + } + }) + .log_err(); + } + }) + .detach(); + } + _ => { + copilot.update(cx, |this, cx| this.sign_in(cx)).detach(); + workspace + .update(cx, |this, cx| { + this.toggle_modal(cx, |cx| CopilotCodeVerification::new(&copilot, cx)); + }) + .ok(); + } + } +} + pub struct CopilotCodeVerification { status: Status, connect_clicked: bool, diff --git a/crates/inline_completion_button/Cargo.toml b/crates/inline_completion_button/Cargo.toml index 13b2bfa2ea..427d0dafd8 100644 --- a/crates/inline_completion_button/Cargo.toml +++ b/crates/inline_completion_button/Cargo.toml @@ -23,7 +23,6 @@ paths.workspace = true settings.workspace = true supermaven.workspace = true ui.workspace = true -util.workspace = true workspace.workspace = true zed_actions.workspace = true diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index 8f727fd2fe..5470678d38 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/crates/inline_completion_button/src/inline_completion_button.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use copilot::{Copilot, CopilotCodeVerification, Status}; +use copilot::{Copilot, Status}; use editor::{scroll::Autoscroll, Editor}; use fs::Fs; use gpui::{ @@ -15,7 +15,6 @@ use language::{ use settings::{update_settings_file, Settings, SettingsStore}; use std::{path::Path, sync::Arc}; use supermaven::{AccountStatus, Supermaven}; -use util::ResultExt; use workspace::{ create_and_open_local_file, item::ItemHandle, @@ -29,8 +28,6 @@ use zed_actions::OpenBrowser; const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot"; -struct CopilotStartingToast; - struct CopilotErrorToast; pub struct InlineCompletionButton { @@ -221,7 +218,7 @@ impl InlineCompletionButton { pub fn build_copilot_start_menu(&mut self, cx: &mut ViewContext) -> View { let fs = self.fs.clone(); ContextMenu::build(cx, |menu, _| { - menu.entry("Sign In", None, initiate_sign_in) + menu.entry("Sign In", None, copilot::initiate_sign_in) .entry("Disable Copilot", None, { let fs = fs.clone(); move |cx| hide_copilot(fs.clone(), cx) @@ -484,68 +481,3 @@ fn hide_copilot(fs: Arc, cx: &mut AppContext) { .inline_completion_provider = Some(InlineCompletionProvider::None); }); } - -pub fn initiate_sign_in(cx: &mut WindowContext) { - let Some(copilot) = Copilot::global(cx) else { - return; - }; - let status = copilot.read(cx).status(); - let Some(workspace) = cx.window_handle().downcast::() else { - return; - }; - match status { - Status::Starting { task } => { - let Some(workspace) = cx.window_handle().downcast::() else { - return; - }; - - let Ok(workspace) = workspace.update(cx, |workspace, cx| { - workspace.show_toast( - Toast::new( - NotificationId::unique::(), - "Copilot is starting...", - ), - cx, - ); - workspace.weak_handle() - }) else { - return; - }; - - cx.spawn(|mut cx| async move { - task.await; - if let Some(copilot) = cx.update(|cx| Copilot::global(cx)).ok().flatten() { - workspace - .update(&mut cx, |workspace, cx| match copilot.read(cx).status() { - Status::Authorized => workspace.show_toast( - Toast::new( - NotificationId::unique::(), - "Copilot has started!", - ), - cx, - ), - _ => { - workspace.dismiss_toast( - &NotificationId::unique::(), - cx, - ); - copilot - .update(cx, |copilot, cx| copilot.sign_in(cx)) - .detach_and_log_err(cx); - } - }) - .log_err(); - } - }) - .detach(); - } - _ => { - copilot.update(cx, |this, cx| this.sign_in(cx)).detach(); - workspace - .update(cx, |this, cx| { - this.toggle_modal(cx, |cx| CopilotCodeVerification::new(&copilot, cx)); - }) - .ok(); - } - } -} diff --git a/crates/language_model/Cargo.toml b/crates/language_model/Cargo.toml index e88675bbae..faca4adcc2 100644 --- a/crates/language_model/Cargo.toml +++ b/crates/language_model/Cargo.toml @@ -32,7 +32,6 @@ futures.workspace = true google_ai = { workspace = true, features = ["schemars"] } gpui.workspace = true http_client.workspace = true -inline_completion_button.workspace = true log.workspace = true menu.workspace = true ollama = { workspace = true, features = ["schemars"] } diff --git a/crates/language_model/src/provider/copilot_chat.rs b/crates/language_model/src/provider/copilot_chat.rs index a991e81fbc..0eaeaa2e3d 100644 --- a/crates/language_model/src/provider/copilot_chat.rs +++ b/crates/language_model/src/provider/copilot_chat.rs @@ -383,9 +383,7 @@ impl Render for ConfigurationView { .icon_size(IconSize::Medium) .style(ui::ButtonStyle::Filled) .full_width() - .on_click(|_, cx| { - inline_completion_button::initiate_sign_in(cx) - }), + .on_click(|_, cx| copilot::initiate_sign_in(cx)), ) .child( div().flex().w_full().items_center().child( diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index 30645d5f12..8ec245290d 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -17,10 +17,10 @@ test-support = [] [dependencies] anyhow.workspace = true client.workspace = true +copilot.workspace = true db.workspace = true fuzzy.workspace = true gpui.workspace = true -inline_completion_button.workspace = true install_cli.workspace = true picker.workspace = true project.workspace = true diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 02ce0750c4..89f12aa37e 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -177,7 +177,7 @@ impl Render for WelcomePage { this.telemetry.report_app_event( "welcome page: sign in to copilot".to_string(), ); - inline_completion_button::initiate_sign_in(cx); + copilot::initiate_sign_in(cx); }), ), ) From 29c9f0f6a1879ac9bdacfa044f821ad46b68b2b9 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 20 Nov 2024 16:51:13 -0500 Subject: [PATCH 075/157] Extract `InlineCompletionProvider` to its own crate (#20935) This PR extracts the `InlineCompletionProvider` trait and its related types out of `editor` and into a new `inline_completion` crate. By doing so we're able to remove a dependency on `editor` from the `copilot` and `supermaven` crates. We did have to move `editor::Direction` into the `inline_completion` crate, as it is referenced by the `InlineCompletionProvider`. This should find a better home, at some point. Release Notes: - N/A --- Cargo.lock | 13 +++++++++++++ Cargo.toml | 2 ++ crates/copilot/Cargo.toml | 8 ++++---- .../copilot/src/copilot_completion_provider.rs | 2 +- crates/editor/Cargo.toml | 1 + crates/editor/src/editor.rs | 10 ++-------- crates/inline_completion/Cargo.toml | 18 ++++++++++++++++++ crates/inline_completion/LICENSE-GPL | 1 + .../src/inline_completion.rs} | 11 ++++++++++- crates/supermaven/Cargo.toml | 6 +++--- .../src/supermaven_completion_provider.rs | 2 +- 11 files changed, 56 insertions(+), 18 deletions(-) create mode 100644 crates/inline_completion/Cargo.toml create mode 120000 crates/inline_completion/LICENSE-GPL rename crates/{editor/src/inline_completion_provider.rs => inline_completion/src/inline_completion.rs} (93%) diff --git a/Cargo.lock b/Cargo.lock index c0f9fd746f..c27b9b303c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2876,6 +2876,7 @@ dependencies = [ "gpui", "http_client", "indoc", + "inline_completion", "language", "lsp", "menu", @@ -3721,6 +3722,7 @@ dependencies = [ "gpui", "http_client", "indoc", + "inline_completion", "itertools 0.13.0", "language", "linkify", @@ -6056,6 +6058,16 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "inline_completion" +version = "0.1.0" +dependencies = [ + "gpui", + "language", + "project", + "text", +] + [[package]] name = "inline_completion_button" version = "0.1.0" @@ -11781,6 +11793,7 @@ dependencies = [ "futures 0.3.31", "gpui", "http_client", + "inline_completion", "language", "log", "postage", diff --git a/Cargo.toml b/Cargo.toml index 98922a7ca2..252549d116 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ members = [ "crates/http_client", "crates/image_viewer", "crates/indexed_docs", + "crates/inline_completion", "crates/inline_completion_button", "crates/install_cli", "crates/journal", @@ -221,6 +222,7 @@ html_to_markdown = { path = "crates/html_to_markdown" } http_client = { path = "crates/http_client" } image_viewer = { path = "crates/image_viewer" } indexed_docs = { path = "crates/indexed_docs" } +inline_completion = { path = "crates/inline_completion" } inline_completion_button = { path = "crates/inline_completion_button" } install_cli = { path = "crates/install_cli" } journal = { path = "crates/journal" } diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml index 2a54497562..2cbe76c16e 100644 --- a/crates/copilot/Cargo.toml +++ b/crates/copilot/Cargo.toml @@ -29,14 +29,14 @@ anyhow.workspace = true async-compression.workspace = true async-tar.workspace = true chrono.workspace = true -collections.workspace = true client.workspace = true +collections.workspace = true command_palette_hooks.workspace = true -editor.workspace = true fs.workspace = true futures.workspace = true gpui.workspace = true http_client.workspace = true +inline_completion.workspace = true language.workspace = true lsp.workspace = true menu.workspace = true @@ -44,12 +44,12 @@ node_runtime.workspace = true parking_lot.workspace = true paths.workspace = true project.workspace = true +schemars = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true -schemars = { workspace = true, optional = true } -strum.workspace = true settings.workspace = true smol.workspace = true +strum.workspace = true task.workspace = true ui.workspace = true util.workspace = true diff --git a/crates/copilot/src/copilot_completion_provider.rs b/crates/copilot/src/copilot_completion_provider.rs index 059d3a4236..85fe20f1ae 100644 --- a/crates/copilot/src/copilot_completion_provider.rs +++ b/crates/copilot/src/copilot_completion_provider.rs @@ -1,8 +1,8 @@ use crate::{Completion, Copilot}; use anyhow::Result; use client::telemetry::Telemetry; -use editor::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider}; use gpui::{AppContext, EntityId, Model, ModelContext, Task}; +use inline_completion::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider}; use language::{ language_settings::{all_language_settings, AllLanguageSettings}, Buffer, OffsetRangeExt, ToOffset, diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index a27ac97d41..8d03fa79f0 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -46,6 +46,7 @@ git.workspace = true gpui.workspace = true http_client.workspace = true indoc.workspace = true +inline_completion.workspace = true itertools.workspace = true language.workspace = true linkify.workspace = true diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7f31cdedd3..1435681587 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -28,7 +28,6 @@ mod hover_popover; mod hunk_diff; mod indent_guides; mod inlay_hint_cache; -mod inline_completion_provider; pub mod items; mod linked_editing_ranges; mod lsp_ext; @@ -87,7 +86,8 @@ pub(crate) use hunk_diff::HoveredHunk; use hunk_diff::{diff_hunk_to_display, ExpandedHunks}; use indent_guides::ActiveIndentGuidesState; use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; -pub use inline_completion_provider::*; +pub use inline_completion::Direction; +use inline_completion::{InlayProposal, InlineCompletionProvider, InlineCompletionProviderHandle}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; use language::{ @@ -273,12 +273,6 @@ enum DocumentHighlightRead {} enum DocumentHighlightWrite {} enum InputComposition {} -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum Direction { - Prev, - Next, -} - #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Navigated { Yes, diff --git a/crates/inline_completion/Cargo.toml b/crates/inline_completion/Cargo.toml new file mode 100644 index 0000000000..237b0ff43f --- /dev/null +++ b/crates/inline_completion/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "inline_completion" +version = "0.1.0" +edition = "2021" +publish = false +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/inline_completion.rs" + +[dependencies] +gpui.workspace = true +language.workspace = true +project.workspace = true +text.workspace = true diff --git a/crates/inline_completion/LICENSE-GPL b/crates/inline_completion/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/inline_completion/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/editor/src/inline_completion_provider.rs b/crates/inline_completion/src/inline_completion.rs similarity index 93% rename from crates/editor/src/inline_completion_provider.rs rename to crates/inline_completion/src/inline_completion.rs index 1085a6294e..689bc03174 100644 --- a/crates/editor/src/inline_completion_provider.rs +++ b/crates/inline_completion/src/inline_completion.rs @@ -1,9 +1,18 @@ -use crate::Direction; use gpui::{AppContext, Model, ModelContext}; use language::Buffer; use std::ops::Range; use text::{Anchor, Rope}; +// TODO: Find a better home for `Direction`. +// +// This should live in an ancestor crate of `editor` and `inline_completion`, +// but at time of writing there isn't an obvious spot. +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum Direction { + Prev, + Next, +} + pub enum InlayProposal { Hint(Anchor, project::InlayHint), Suggestion(Anchor, Rope), diff --git a/crates/supermaven/Cargo.toml b/crates/supermaven/Cargo.toml index e04d0ef51b..fd0adb0d98 100644 --- a/crates/supermaven/Cargo.toml +++ b/crates/supermaven/Cargo.toml @@ -16,17 +16,17 @@ doctest = false anyhow.workspace = true client.workspace = true collections.workspace = true -editor.workspace = true -gpui.workspace = true futures.workspace = true +gpui.workspace = true +inline_completion.workspace = true language.workspace = true log.workspace = true postage.workspace = true serde.workspace = true serde_json.workspace = true settings.workspace = true -supermaven_api.workspace = true smol.workspace = true +supermaven_api.workspace = true text.workspace = true ui.workspace = true unicode-segmentation.workspace = true diff --git a/crates/supermaven/src/supermaven_completion_provider.rs b/crates/supermaven/src/supermaven_completion_provider.rs index b9185c9762..5e77cc21ef 100644 --- a/crates/supermaven/src/supermaven_completion_provider.rs +++ b/crates/supermaven/src/supermaven_completion_provider.rs @@ -1,9 +1,9 @@ use crate::{Supermaven, SupermavenCompletionStateId}; use anyhow::Result; use client::telemetry::Telemetry; -use editor::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider}; use futures::StreamExt as _; use gpui::{AppContext, EntityId, Model, ModelContext, Task}; +use inline_completion::{CompletionProposal, Direction, InlayProposal, InlineCompletionProvider}; use language::{language_settings::all_language_settings, Anchor, Buffer, BufferSnapshot}; use std::{ ops::{AddAssign, Range}, From ebca6a8f3d151e8b5af5922a2fc3055870feff01 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 20 Nov 2024 15:34:24 -0700 Subject: [PATCH 076/157] Send os_version and country to amplitude (#20936) Release Notes: - N/A --- crates/client/src/telemetry.rs | 2 ++ crates/collab/src/api/events.rs | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index fcb9ced4e5..583f9757c4 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -224,6 +224,8 @@ impl Telemetry { cx.background_executor() .spawn({ let state = state.clone(); + let os_version = os_version(); + state.lock().os_version = Some(os_version.clone()); async move { if let Some(tempfile) = File::create(Self::log_file_path()).log_err() { state.lock().log_file = Some(tempfile); diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 1c936bac39..2679193cad 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -1555,15 +1555,15 @@ fn for_snowflake( ); map.insert("signed_in".to_string(), event.signed_in.into()); if let Some(country_code) = country_code.as_ref() { - map.insert("country_code".to_string(), country_code.clone().into()); + map.insert("country".to_string(), country_code.clone().into()); } } + // NOTE: most amplitude user properties are read out of our event_properties + // dictionary. See https://app.amplitude.com/data/zed/Zed/sources/detail/production/falcon%3A159998 + // for how that is configured. let user_properties = Some(serde_json::json!({ "is_staff": body.is_staff, - "Country": country_code.clone(), - "OS": format!("{} {}", body.os_name, body.os_version.clone().unwrap_or_default()), - "Version": body.app_version.clone(), })); Some(SnowflakeRow { From 427c2017c3b43e3f48ee6e7ce42c4c789c5517cb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:34:59 -0700 Subject: [PATCH 077/157] Update Rust crate serde_json to v1.0.133 (#20932) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [serde_json](https://redirect.github.com/serde-rs/json) | dependencies | patch | `1.0.132` -> `1.0.133` | | [serde_json](https://redirect.github.com/serde-rs/json) | workspace.dependencies | patch | `1.0.132` -> `1.0.133` | --- ### Release Notes
serde-rs/json (serde_json) ### [`v1.0.133`](https://redirect.github.com/serde-rs/json/releases/tag/v1.0.133) [Compare Source](https://redirect.github.com/serde-rs/json/compare/v1.0.132...v1.0.133) - Implement From<\[T; N]> for serde_json::Value ([#​1215](https://redirect.github.com/serde-rs/json/issues/1215))
--- ### Configuration 📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone America/New_York, Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] If you want to rebase/retry this PR, check this box --- Release Notes: - N/A Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c27b9b303c..6d38e2f6b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10864,9 +10864,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "indexmap 2.6.0", "itoa", From 6d4a5f9ad2d53f915040064472fe6cb38743af2f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:35:08 -0700 Subject: [PATCH 078/157] Update Rust crate libc to v0.2.164 (#20931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [libc](https://redirect.github.com/rust-lang/libc) | workspace.dependencies | patch | `0.2.162` -> `0.2.164` | --- ### Release Notes
rust-lang/libc (libc) ### [`v0.2.164`](https://redirect.github.com/rust-lang/libc/blob/HEAD/CHANGELOG.md#02164---2024-11-16) [Compare Source](https://redirect.github.com/rust-lang/libc/compare/0.2.163...0.2.164) ##### MSRV This release increases the MSRV of `libc` to 1.63. ##### Other - CI: remove tests with rust < 1.63 [#​4051](https://redirect.github.com/rust-lang/libc/pull/4051) - MSRV: document the MSRV of the stable channel to be 1.63 [#​4040](https://redirect.github.com/rust-lang/libc/pull/4040) - MacOS: move ifconf to s_no_extra_traits [#​4051](https://redirect.github.com/rust-lang/libc/pull/4051) ### [`v0.2.163`](https://redirect.github.com/rust-lang/libc/blob/HEAD/CHANGELOG.md#02163---2024-11-16) [Compare Source](https://redirect.github.com/rust-lang/libc/compare/0.2.162...0.2.163) ##### Added - Aix: add more `dlopen` flags [#​4044](https://redirect.github.com/rust-lang/libc/pull/4044) - Android: add group calls [#​3499](https://redirect.github.com/rust-lang/libc/pull/3499) - FreeBSD: add `TCP_FUNCTION_BLK` and `TCP_FUNCTION_ALIAS` [#​4047](https://redirect.github.com/rust-lang/libc/pull/4047) - Linux: add `confstr` [#​3612](https://redirect.github.com/rust-lang/libc/pull/3612) - Solarish: add `aio` [#​4033](https://redirect.github.com/rust-lang/libc/pull/4033) - Solarish: add `arc4random*` [#​3944](https://redirect.github.com/rust-lang/libc/pull/3944) ##### Changed - Emscripten: upgrade emsdk to 3.1.68 [#​3962](https://redirect.github.com/rust-lang/libc/pull/3962) - Hurd: use more standard types [#​3733](https://redirect.github.com/rust-lang/libc/pull/3733) - Hurd: use the standard `ssize_t = isize` [#​4029](https://redirect.github.com/rust-lang/libc/pull/4029) - Solaris: fix `confstr` and `ucontext_t` [#​4035](https://redirect.github.com/rust-lang/libc/pull/4035) ##### Other - CI: add Solaris [#​4035](https://redirect.github.com/rust-lang/libc/pull/4035) - CI: add `i686-unknown-freebsd` [#​3997](https://redirect.github.com/rust-lang/libc/pull/3997) - CI: ensure that calls to `sort` do not depend on locale [#​4026](https://redirect.github.com/rust-lang/libc/pull/4026) - Specify `rust-version` in `Cargo.toml` [#​4041](https://redirect.github.com/rust-lang/libc/pull/4041)
--- ### Configuration 📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone America/New_York, Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- Release Notes: - N/A Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d38e2f6b1..6c0e7b4614 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6680,9 +6680,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.162" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "libdbus-sys" From 33bed8d680ffcf0c19ebf442a44e4223099da02f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:36:27 -0700 Subject: [PATCH 079/157] Update Rust crate ctor to v0.2.9 (#20928) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [ctor](https://redirect.github.com/mmastrac/rust-ctor) | workspace.dependencies | patch | `0.2.8` -> `0.2.9` | --- ### Configuration 📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone America/New_York, Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- Release Notes: - N/A Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c0e7b4614..429c80e9d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3351,9 +3351,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", "syn 2.0.87", From 335b112abda15767c2540dc011d9ce404be93522 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 21 Nov 2024 00:43:03 +0100 Subject: [PATCH 080/157] title_bar: Remove dependency on recent_projects (#20942) Use actions defined in zed_actions to interface with that crate instead. One drawback of this is that we now hide call controls when any modal is visible (we used to hide them just when ssh modal was deployed). Release Notes: - N/A --- Cargo.lock | 2 +- crates/recent_projects/Cargo.toml | 1 + crates/recent_projects/src/recent_projects.rs | 15 +-------------- crates/title_bar/Cargo.toml | 1 - crates/title_bar/src/application_menu.rs | 2 +- crates/title_bar/src/collab.rs | 4 +--- crates/title_bar/src/title_bar.rs | 17 ++++++++--------- crates/zed/src/zed/app_menus.rs | 2 +- crates/zed_actions/src/lib.rs | 8 ++++++++ 9 files changed, 22 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 429c80e9d4..3cf7a59177 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9684,6 +9684,7 @@ dependencies = [ "ui", "util", "workspace", + "zed_actions", ] [[package]] @@ -12594,7 +12595,6 @@ dependencies = [ "notifications", "pretty_assertions", "project", - "recent_projects", "remote", "rpc", "serde", diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index b1759de778..827afff7c0 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -40,6 +40,7 @@ ui.workspace = true util.workspace = true workspace.workspace = true paths.workspace = true +zed_actions.workspace = true [dev-dependencies] editor = { workspace = true, features = ["test-support"] } diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index e01309cacd..c08136cdf5 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -16,7 +16,6 @@ use picker::{ Picker, PickerDelegate, }; pub use remote_servers::RemoteServerProjects; -use serde::Deserialize; use settings::Settings; pub use ssh_connections::SshSettings; use std::{ @@ -29,19 +28,7 @@ use workspace::{ CloseIntent, ModalView, OpenOptions, SerializedWorkspaceLocation, Workspace, WorkspaceId, WORKSPACE_DB, }; - -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct OpenRecent { - #[serde(default = "default_create_new_window")] - pub create_new_window: bool, -} - -fn default_create_new_window() -> bool { - false -} - -gpui::impl_actions!(projects, [OpenRecent]); -gpui::actions!(projects, [OpenRemote]); +use zed_actions::{OpenRecent, OpenRemote}; pub fn init(cx: &mut AppContext) { SshSettings::register(cx); diff --git a/crates/title_bar/Cargo.toml b/crates/title_bar/Cargo.toml index 569231bb9c..05bd1be502 100644 --- a/crates/title_bar/Cargo.toml +++ b/crates/title_bar/Cargo.toml @@ -37,7 +37,6 @@ feature_flags.workspace = true gpui.workspace = true notifications.workspace = true project.workspace = true -recent_projects.workspace = true remote.workspace = true rpc.workspace = true serde.workspace = true diff --git a/crates/title_bar/src/application_menu.rs b/crates/title_bar/src/application_menu.rs index 13ee10c141..c3994f81d7 100644 --- a/crates/title_bar/src/application_menu.rs +++ b/crates/title_bar/src/application_menu.rs @@ -100,7 +100,7 @@ impl Render for ApplicationMenu { .action("Open a new Project...", Box::new(workspace::Open)) .action( "Open Recent Projects...", - Box::new(recent_projects::OpenRecent { + Box::new(zed_actions::OpenRecent { create_new_window: false, }), ) diff --git a/crates/title_bar/src/collab.rs b/crates/title_bar/src/collab.rs index 805c0e7202..649dfb34f7 100644 --- a/crates/title_bar/src/collab.rs +++ b/crates/title_bar/src/collab.rs @@ -284,9 +284,7 @@ impl TitleBar { let is_connecting_to_project = self .workspace - .update(cx, |workspace, cx| { - recent_projects::is_connecting_over_ssh(workspace, cx) - }) + .update(cx, |workspace, cx| workspace.has_active_modal(cx)) .unwrap_or(false); let room = room.read(cx); diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 44301520ac..bcf13a5ac7 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -18,7 +18,6 @@ use gpui::{ StatefulInteractiveElement, Styled, Subscription, View, ViewContext, VisualContext, WeakView, }; use project::{Project, RepositoryEntry}; -use recent_projects::{OpenRemote, RecentProjects}; use rpc::proto; use smallvec::SmallVec; use std::sync::Arc; @@ -30,7 +29,7 @@ use ui::{ use util::ResultExt; use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu}; use workspace::{notifications::NotifyResultExt, Workspace}; -use zed_actions::OpenBrowser; +use zed_actions::{OpenBrowser, OpenRecent, OpenRemote}; #[cfg(feature = "stories")] pub use stories::*; @@ -397,7 +396,6 @@ impl TitleBar { "Open recent project".to_string() }; - let workspace = self.workspace.clone(); Button::new("project_name_trigger", name) .when(!is_project_selected, |b| b.color(Color::Muted)) .style(ButtonStyle::Subtle) @@ -405,18 +403,19 @@ impl TitleBar { .tooltip(move |cx| { Tooltip::for_action( "Recent Projects", - &recent_projects::OpenRecent { + &zed_actions::OpenRecent { create_new_window: false, }, cx, ) }) .on_click(cx.listener(move |_, _, cx| { - if let Some(workspace) = workspace.upgrade() { - workspace.update(cx, |workspace, cx| { - RecentProjects::open(workspace, false, cx); - }) - } + cx.dispatch_action( + OpenRecent { + create_new_window: false, + } + .boxed_clone(), + ); })) } diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index 09e21f20ab..824704fca5 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -50,7 +50,7 @@ pub fn app_menus() -> Vec { MenuItem::action("Open…", workspace::Open), MenuItem::action( "Open Recent...", - recent_projects::OpenRecent { + zed_actions::OpenRecent { create_new_window: true, }, ), diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index bbe774652e..2f33583429 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -50,3 +50,11 @@ pub struct InlineAssist { } impl_actions!(assistant, [InlineAssist]); + +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct OpenRecent { + #[serde(default)] + pub create_new_window: bool, +} +gpui::impl_actions!(projects, [OpenRecent]); +gpui::actions!(projects, [OpenRemote]); From cbba44900d07f12142df0ba2cd90534f2d83d815 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 20 Nov 2024 18:49:34 -0500 Subject: [PATCH 081/157] Add `language_models` crate to house language model providers (#20945) This PR adds a new `language_models` crate to house the various language model providers. By extracting the provider definitions out of `language_model`, we're able to remove `language_model`'s dependency on `editor`, which improves incremental compilation when changing `editor`. Release Notes: - N/A --- Cargo.lock | 43 +++++++--- Cargo.toml | 2 + crates/assistant/Cargo.toml | 1 + crates/assistant/src/assistant_panel.rs | 10 +-- crates/assistant/src/assistant_settings.rs | 11 ++- crates/assistant/src/context.rs | 6 +- crates/assistant/src/inline_assistant.rs | 5 +- .../src/terminal_inline_assistant.rs | 4 +- crates/language_model/Cargo.toml | 34 +------- .../{provider/fake.rs => fake_provider.rs} | 0 crates/language_model/src/language_model.rs | 28 +++---- crates/language_model/src/registry.rs | 75 ++--------------- crates/language_models/Cargo.toml | 49 ++++++++++++ crates/language_models/LICENSE-GPL | 1 + crates/language_models/src/language_models.rs | 80 +++++++++++++++++++ .../src/logging.rs | 0 .../src/provider.rs | 2 - .../src/provider/anthropic.rs | 15 ++-- .../src/provider/cloud.rs | 22 ++--- .../src/provider/copilot_chat.rs | 11 ++- .../src/provider/google.rs | 13 +-- .../src/provider/ollama.rs | 13 +-- .../src/provider/open_ai.rs | 12 +-- .../src/settings.rs | 20 +++-- crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 3 +- crates/zed/src/zed.rs | 3 +- 27 files changed, 265 insertions(+), 199 deletions(-) rename crates/language_model/src/{provider/fake.rs => fake_provider.rs} (100%) create mode 100644 crates/language_models/Cargo.toml create mode 120000 crates/language_models/LICENSE-GPL create mode 100644 crates/language_models/src/language_models.rs rename crates/{language_model => language_models}/src/logging.rs (100%) rename crates/{language_model => language_models}/src/provider.rs (64%) rename crates/{language_model => language_models}/src/provider/anthropic.rs (98%) rename crates/{language_model => language_models}/src/provider/cloud.rs (98%) rename crates/{language_model => language_models}/src/provider/copilot_chat.rs (98%) rename crates/{language_model => language_models}/src/provider/google.rs (98%) rename crates/{language_model => language_models}/src/provider/ollama.rs (98%) rename crates/{language_model => language_models}/src/provider/open_ai.rs (99%) rename crates/{language_model => language_models}/src/settings.rs (97%) diff --git a/Cargo.lock b/Cargo.lock index 3cf7a59177..a8ff3abe01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,6 +402,7 @@ dependencies = [ "indoc", "language", "language_model", + "language_models", "languages", "log", "lsp", @@ -6520,27 +6521,48 @@ dependencies = [ "anthropic", "anyhow", "base64 0.22.1", - "client", "collections", - "copilot", - "ctor", - "editor", - "env_logger 0.11.5", - "feature_flags", "futures 0.3.31", "google_ai", "gpui", "http_client", "image", - "language", "log", - "menu", "ollama", "open_ai", "parking_lot", + "proto", + "schemars", + "serde", + "serde_json", + "smol", + "strum 0.25.0", + "ui", + "util", +] + +[[package]] +name = "language_models" +version = "0.1.0" +dependencies = [ + "anthropic", + "anyhow", + "client", + "collections", + "copilot", + "editor", + "feature_flags", + "fs", + "futures 0.3.31", + "google_ai", + "gpui", + "http_client", + "language_model", + "menu", + "ollama", + "open_ai", "project", "proto", - "rand 0.8.5", "schemars", "serde", "serde_json", @@ -6548,12 +6570,10 @@ dependencies = [ "smol", "strum 0.25.0", "telemetry_events", - "text", "theme", "thiserror 1.0.69", "tiktoken-rs", "ui", - "unindent", "util", ] @@ -15481,6 +15501,7 @@ dependencies = [ "journal", "language", "language_model", + "language_models", "language_selector", "language_tools", "languages", diff --git a/Cargo.toml b/Cargo.toml index 252549d116..8357160268 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ members = [ "crates/journal", "crates/language", "crates/language_model", + "crates/language_models", "crates/language_selector", "crates/language_tools", "crates/languages", @@ -228,6 +229,7 @@ install_cli = { path = "crates/install_cli" } journal = { path = "crates/journal" } language = { path = "crates/language" } language_model = { path = "crates/language_model" } +language_models = { path = "crates/language_models" } language_selector = { path = "crates/language_selector" } language_tools = { path = "crates/language_tools" } languages = { path = "crates/languages" } diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 21153b6fcc..7f5aef3f46 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -50,6 +50,7 @@ indexed_docs.workspace = true indoc.workspace = true language.workspace = true language_model.workspace = true +language_models.workspace = true log.workspace = true lsp.workspace = true markdown.workspace = true diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index c89595c7da..ff60f2b918 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -50,11 +50,11 @@ use indexed_docs::IndexedDocsStore; use language::{ language_settings::SoftWrap, BufferSnapshot, LanguageRegistry, LspAdapterDelegate, ToOffset, }; -use language_model::{ - provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId, - LanguageModelRegistry, Role, -}; use language_model::{LanguageModelImage, LanguageModelToolUse}; +use language_model::{ + LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role, + ZED_CLOUD_PROVIDER_ID, +}; use multi_buffer::MultiBufferRow; use picker::{Picker, PickerDelegate}; use project::lsp_store::LocalLspAdapterDelegate; @@ -664,7 +664,7 @@ impl AssistantPanel { // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is // the provider, we want to show a nudge to sign in. let show_zed_ai_notice = client_status.is_signed_out() - && active_provider.map_or(true, |provider| provider.id().0 == PROVIDER_ID); + && active_provider.map_or(true, |provider| provider.id().0 == ZED_CLOUD_PROVIDER_ID); self.show_zed_ai_notice = show_zed_ai_notice; cx.notify(); diff --git a/crates/assistant/src/assistant_settings.rs b/crates/assistant/src/assistant_settings.rs index 98188305fb..a782f05d03 100644 --- a/crates/assistant/src/assistant_settings.rs +++ b/crates/assistant/src/assistant_settings.rs @@ -5,13 +5,12 @@ use anthropic::Model as AnthropicModel; use feature_flags::FeatureFlagAppExt; use fs::Fs; use gpui::{AppContext, Pixels}; -use language_model::provider::open_ai; -use language_model::settings::{ - AnthropicSettingsContent, AnthropicSettingsContentV1, OllamaSettingsContent, - OpenAiSettingsContent, OpenAiSettingsContentV1, VersionedAnthropicSettingsContent, - VersionedOpenAiSettingsContent, +use language_model::{CloudModel, LanguageModel}; +use language_models::{ + provider::open_ai, AllLanguageModelSettings, AnthropicSettingsContent, + AnthropicSettingsContentV1, OllamaSettingsContent, OpenAiSettingsContent, + OpenAiSettingsContentV1, VersionedAnthropicSettingsContent, VersionedOpenAiSettingsContent, }; -use language_model::{settings::AllLanguageModelSettings, CloudModel, LanguageModel}; use ollama::Model as OllamaModel; use schemars::{schema::Schema, JsonSchema}; use serde::{Deserialize, Serialize}; diff --git a/crates/assistant/src/context.rs b/crates/assistant/src/context.rs index 39c31d7c58..570180ed74 100644 --- a/crates/assistant/src/context.rs +++ b/crates/assistant/src/context.rs @@ -25,13 +25,15 @@ use gpui::{ use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset}; use language_model::{ - logging::report_assistant_event, - provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError}, LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent, LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUse, MessageContent, Role, StopReason, }; +use language_models::{ + provider::cloud::{MaxMonthlySpendReachedError, PaymentRequiredError}, + report_assistant_event, +}; use open_ai::Model as OpenAiModel; use paths::contexts_dir; use project::Project; diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index 22620ca2c2..855972c267 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -30,9 +30,10 @@ use gpui::{ }; use language::{Buffer, IndentKind, Point, Selection, TransactionId}; use language_model::{ - logging::report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest, - LanguageModelRequestMessage, LanguageModelTextStream, Role, + LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, + LanguageModelTextStream, Role, }; +use language_models::report_assistant_event; use multi_buffer::MultiBufferRow; use parking_lot::Mutex; use project::{CodeAction, ProjectTransaction}; diff --git a/crates/assistant/src/terminal_inline_assistant.rs b/crates/assistant/src/terminal_inline_assistant.rs index 2fb4b4ffda..51738b90e4 100644 --- a/crates/assistant/src/terminal_inline_assistant.rs +++ b/crates/assistant/src/terminal_inline_assistant.rs @@ -17,9 +17,9 @@ use gpui::{ }; use language::Buffer; use language_model::{ - logging::report_assistant_event, LanguageModelRegistry, LanguageModelRequest, - LanguageModelRequestMessage, Role, + LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role, }; +use language_models::report_assistant_event; use settings::Settings; use std::{ cmp, diff --git a/crates/language_model/Cargo.toml b/crates/language_model/Cargo.toml index faca4adcc2..0fc54d509d 100644 --- a/crates/language_model/Cargo.toml +++ b/crates/language_model/Cargo.toml @@ -13,56 +13,30 @@ path = "src/language_model.rs" doctest = false [features] -test-support = [ - "editor/test-support", - "language/test-support", - "project/test-support", - "text/test-support", -] +test-support = [] [dependencies] anthropic = { workspace = true, features = ["schemars"] } anyhow.workspace = true -client.workspace = true +base64.workspace = true collections.workspace = true -copilot = { workspace = true, features = ["schemars"] } -editor.workspace = true -feature_flags.workspace = true futures.workspace = true google_ai = { workspace = true, features = ["schemars"] } gpui.workspace = true http_client.workspace = true +image.workspace = true log.workspace = true -menu.workspace = true ollama = { workspace = true, features = ["schemars"] } open_ai = { workspace = true, features = ["schemars"] } parking_lot.workspace = true proto.workspace = true -project.workspace = true schemars.workspace = true serde.workspace = true serde_json.workspace = true -settings.workspace = true smol.workspace = true strum.workspace = true -telemetry_events.workspace = true -theme.workspace = true -thiserror.workspace = true -tiktoken-rs.workspace = true ui.workspace = true util.workspace = true -base64.workspace = true -image.workspace = true - [dev-dependencies] -ctor.workspace = true -editor = { workspace = true, features = ["test-support"] } -env_logger.workspace = true -language = { workspace = true, features = ["test-support"] } -log.workspace = true -project = { workspace = true, features = ["test-support"] } -proto = { workspace = true, features = ["test-support"] } -rand.workspace = true -text = { workspace = true, features = ["test-support"] } -unindent.workspace = true +gpui = { workspace = true, features = ["test-support"] } diff --git a/crates/language_model/src/provider/fake.rs b/crates/language_model/src/fake_provider.rs similarity index 100% rename from crates/language_model/src/provider/fake.rs rename to crates/language_model/src/fake_provider.rs diff --git a/crates/language_model/src/language_model.rs b/crates/language_model/src/language_model.rs index a2f5a072a9..f9df34a2d1 100644 --- a/crates/language_model/src/language_model.rs +++ b/crates/language_model/src/language_model.rs @@ -1,23 +1,19 @@ -pub mod logging; mod model; -pub mod provider; mod rate_limiter; mod registry; mod request; mod role; -pub mod settings; + +#[cfg(any(test, feature = "test-support"))] +pub mod fake_provider; use anyhow::Result; -use client::{Client, UserStore}; use futures::FutureExt; use futures::{future::BoxFuture, stream::BoxStream, StreamExt, TryStreamExt as _}; -use gpui::{ - AnyElement, AnyView, AppContext, AsyncAppContext, Model, SharedString, Task, WindowContext, -}; +use gpui::{AnyElement, AnyView, AppContext, AsyncAppContext, SharedString, Task, WindowContext}; pub use model::*; -use project::Fs; use proto::Plan; -pub(crate) use rate_limiter::*; +pub use rate_limiter::*; pub use registry::*; pub use request::*; pub use role::*; @@ -27,14 +23,10 @@ use std::fmt; use std::{future::Future, sync::Arc}; use ui::IconName; -pub fn init( - user_store: Model, - client: Arc, - fs: Arc, - cx: &mut AppContext, -) { - settings::init(fs, cx); - registry::init(user_store, client, cx); +pub const ZED_CLOUD_PROVIDER_ID: &str = "zed.dev"; + +pub fn init(cx: &mut AppContext) { + registry::init(cx); } /// The availability of a [`LanguageModel`]. @@ -184,7 +176,7 @@ pub trait LanguageModel: Send + Sync { } #[cfg(any(test, feature = "test-support"))] - fn as_fake(&self) -> &provider::fake::FakeLanguageModel { + fn as_fake(&self) -> &fake_provider::FakeLanguageModel { unimplemented!() } } diff --git a/crates/language_model/src/registry.rs b/crates/language_model/src/registry.rs index 72dfd998d4..88b2e8301c 100644 --- a/crates/language_model/src/registry.rs +++ b/crates/language_model/src/registry.rs @@ -1,76 +1,17 @@ -use crate::provider::cloud::RefreshLlmTokenListener; use crate::{ - provider::{ - anthropic::AnthropicLanguageModelProvider, cloud::CloudLanguageModelProvider, - copilot_chat::CopilotChatLanguageModelProvider, google::GoogleLanguageModelProvider, - ollama::OllamaLanguageModelProvider, open_ai::OpenAiLanguageModelProvider, - }, LanguageModel, LanguageModelId, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderState, }; -use client::{Client, UserStore}; use collections::BTreeMap; use gpui::{AppContext, EventEmitter, Global, Model, ModelContext}; use std::sync::Arc; use ui::Context; -pub fn init(user_store: Model, client: Arc, cx: &mut AppContext) { - let registry = cx.new_model(|cx| { - let mut registry = LanguageModelRegistry::default(); - register_language_model_providers(&mut registry, user_store, client, cx); - registry - }); +pub fn init(cx: &mut AppContext) { + let registry = cx.new_model(|_cx| LanguageModelRegistry::default()); cx.set_global(GlobalLanguageModelRegistry(registry)); } -fn register_language_model_providers( - registry: &mut LanguageModelRegistry, - user_store: Model, - client: Arc, - cx: &mut ModelContext, -) { - use feature_flags::FeatureFlagAppExt; - - RefreshLlmTokenListener::register(client.clone(), cx); - - registry.register_provider( - AnthropicLanguageModelProvider::new(client.http_client(), cx), - cx, - ); - registry.register_provider( - OpenAiLanguageModelProvider::new(client.http_client(), cx), - cx, - ); - registry.register_provider( - OllamaLanguageModelProvider::new(client.http_client(), cx), - cx, - ); - registry.register_provider( - GoogleLanguageModelProvider::new(client.http_client(), cx), - cx, - ); - registry.register_provider(CopilotChatLanguageModelProvider::new(cx), cx); - - cx.observe_flag::(move |enabled, cx| { - let user_store = user_store.clone(); - let client = client.clone(); - LanguageModelRegistry::global(cx).update(cx, move |registry, cx| { - if enabled { - registry.register_provider( - CloudLanguageModelProvider::new(user_store.clone(), client.clone(), cx), - cx, - ); - } else { - registry.unregister_provider( - LanguageModelProviderId::from(crate::provider::cloud::PROVIDER_ID.to_string()), - cx, - ); - } - }); - }) - .detach(); -} - struct GlobalLanguageModelRegistry(Model); impl Global for GlobalLanguageModelRegistry {} @@ -106,8 +47,8 @@ impl LanguageModelRegistry { } #[cfg(any(test, feature = "test-support"))] - pub fn test(cx: &mut AppContext) -> crate::provider::fake::FakeLanguageModelProvider { - let fake_provider = crate::provider::fake::FakeLanguageModelProvider; + pub fn test(cx: &mut AppContext) -> crate::fake_provider::FakeLanguageModelProvider { + let fake_provider = crate::fake_provider::FakeLanguageModelProvider; let registry = cx.new_model(|cx| { let mut registry = Self::default(); registry.register_provider(fake_provider.clone(), cx); @@ -148,7 +89,7 @@ impl LanguageModelRegistry { } pub fn providers(&self) -> Vec> { - let zed_provider_id = LanguageModelProviderId(crate::provider::cloud::PROVIDER_ID.into()); + let zed_provider_id = LanguageModelProviderId("zed.dev".into()); let mut providers = Vec::with_capacity(self.providers.len()); if let Some(provider) = self.providers.get(&zed_provider_id) { providers.push(provider.clone()); @@ -269,7 +210,7 @@ impl LanguageModelRegistry { #[cfg(test)] mod tests { use super::*; - use crate::provider::fake::FakeLanguageModelProvider; + use crate::fake_provider::FakeLanguageModelProvider; #[gpui::test] fn test_register_providers(cx: &mut AppContext) { @@ -281,10 +222,10 @@ mod tests { let providers = registry.read(cx).providers(); assert_eq!(providers.len(), 1); - assert_eq!(providers[0].id(), crate::provider::fake::provider_id()); + assert_eq!(providers[0].id(), crate::fake_provider::provider_id()); registry.update(cx, |registry, cx| { - registry.unregister_provider(crate::provider::fake::provider_id(), cx); + registry.unregister_provider(crate::fake_provider::provider_id(), cx); }); let providers = registry.read(cx).providers(); diff --git a/crates/language_models/Cargo.toml b/crates/language_models/Cargo.toml new file mode 100644 index 0000000000..00d948bd2d --- /dev/null +++ b/crates/language_models/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "language_models" +version = "0.1.0" +edition = "2021" +publish = false +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/language_models.rs" + +[dependencies] +anthropic = { workspace = true, features = ["schemars"] } +anyhow.workspace = true +client.workspace = true +collections.workspace = true +copilot = { workspace = true, features = ["schemars"] } +editor.workspace = true +feature_flags.workspace = true +fs.workspace = true +futures.workspace = true +google_ai = { workspace = true, features = ["schemars"] } +gpui.workspace = true +http_client.workspace = true +language_model.workspace = true +menu.workspace = true +ollama = { workspace = true, features = ["schemars"] } +open_ai = { workspace = true, features = ["schemars"] } +project.workspace = true +proto.workspace = true +schemars.workspace = true +serde.workspace = true +serde_json.workspace = true +settings.workspace = true +smol.workspace = true +strum.workspace = true +telemetry_events.workspace = true +theme.workspace = true +thiserror.workspace = true +tiktoken-rs.workspace = true +ui.workspace = true +util.workspace = true + +[dev-dependencies] +editor = { workspace = true, features = ["test-support"] } +language_model = { workspace = true, features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } diff --git a/crates/language_models/LICENSE-GPL b/crates/language_models/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/language_models/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/language_models/src/language_models.rs b/crates/language_models/src/language_models.rs new file mode 100644 index 0000000000..028ea0cfa4 --- /dev/null +++ b/crates/language_models/src/language_models.rs @@ -0,0 +1,80 @@ +use std::sync::Arc; + +use client::{Client, UserStore}; +use fs::Fs; +use gpui::{AppContext, Model, ModelContext}; +use language_model::{LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID}; + +mod logging; +pub mod provider; +mod settings; + +use crate::provider::anthropic::AnthropicLanguageModelProvider; +use crate::provider::cloud::{CloudLanguageModelProvider, RefreshLlmTokenListener}; +use crate::provider::copilot_chat::CopilotChatLanguageModelProvider; +use crate::provider::google::GoogleLanguageModelProvider; +use crate::provider::ollama::OllamaLanguageModelProvider; +use crate::provider::open_ai::OpenAiLanguageModelProvider; +pub use crate::settings::*; +pub use logging::report_assistant_event; + +pub fn init( + user_store: Model, + client: Arc, + fs: Arc, + cx: &mut AppContext, +) { + crate::settings::init(fs, cx); + let registry = LanguageModelRegistry::global(cx); + registry.update(cx, |registry, cx| { + register_language_model_providers(registry, user_store, client, cx); + }); +} + +fn register_language_model_providers( + registry: &mut LanguageModelRegistry, + user_store: Model, + client: Arc, + cx: &mut ModelContext, +) { + use feature_flags::FeatureFlagAppExt; + + RefreshLlmTokenListener::register(client.clone(), cx); + + registry.register_provider( + AnthropicLanguageModelProvider::new(client.http_client(), cx), + cx, + ); + registry.register_provider( + OpenAiLanguageModelProvider::new(client.http_client(), cx), + cx, + ); + registry.register_provider( + OllamaLanguageModelProvider::new(client.http_client(), cx), + cx, + ); + registry.register_provider( + GoogleLanguageModelProvider::new(client.http_client(), cx), + cx, + ); + registry.register_provider(CopilotChatLanguageModelProvider::new(cx), cx); + + cx.observe_flag::(move |enabled, cx| { + let user_store = user_store.clone(); + let client = client.clone(); + LanguageModelRegistry::global(cx).update(cx, move |registry, cx| { + if enabled { + registry.register_provider( + CloudLanguageModelProvider::new(user_store.clone(), client.clone(), cx), + cx, + ); + } else { + registry.unregister_provider( + LanguageModelProviderId::from(ZED_CLOUD_PROVIDER_ID.to_string()), + cx, + ); + } + }); + }) + .detach(); +} diff --git a/crates/language_model/src/logging.rs b/crates/language_models/src/logging.rs similarity index 100% rename from crates/language_model/src/logging.rs rename to crates/language_models/src/logging.rs diff --git a/crates/language_model/src/provider.rs b/crates/language_models/src/provider.rs similarity index 64% rename from crates/language_model/src/provider.rs rename to crates/language_models/src/provider.rs index d2d162b75e..fb79b12e4d 100644 --- a/crates/language_model/src/provider.rs +++ b/crates/language_models/src/provider.rs @@ -1,8 +1,6 @@ pub mod anthropic; pub mod cloud; pub mod copilot_chat; -#[cfg(any(test, feature = "test-support"))] -pub mod fake; pub mod google; pub mod ollama; pub mod open_ai; diff --git a/crates/language_model/src/provider/anthropic.rs b/crates/language_models/src/provider/anthropic.rs similarity index 98% rename from crates/language_model/src/provider/anthropic.rs rename to crates/language_models/src/provider/anthropic.rs index 60e238b369..87460b824e 100644 --- a/crates/language_model/src/provider/anthropic.rs +++ b/crates/language_models/src/provider/anthropic.rs @@ -1,9 +1,4 @@ -use crate::{ - settings::AllLanguageModelSettings, LanguageModel, LanguageModelCacheConfiguration, - LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId, - LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role, -}; -use crate::{LanguageModelCompletionEvent, LanguageModelToolUse, StopReason}; +use crate::AllLanguageModelSettings; use anthropic::{AnthropicError, ContentDelta, Event, ResponseContent}; use anyhow::{anyhow, Context as _, Result}; use collections::{BTreeMap, HashMap}; @@ -15,6 +10,12 @@ use gpui::{ View, WhiteSpace, }; use http_client::HttpClient; +use language_model::{ + LanguageModel, LanguageModelCacheConfiguration, LanguageModelId, LanguageModelName, + LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, + LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role, +}; +use language_model::{LanguageModelCompletionEvent, LanguageModelToolUse, StopReason}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; @@ -256,7 +257,7 @@ pub fn count_anthropic_tokens( let mut string_messages = Vec::with_capacity(messages.len()); for message in messages { - use crate::MessageContent; + use language_model::MessageContent; let mut string_contents = String::new(); diff --git a/crates/language_model/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs similarity index 98% rename from crates/language_model/src/provider/cloud.rs rename to crates/language_models/src/provider/cloud.rs index 41e23b56e3..f54e8c8d19 100644 --- a/crates/language_model/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -1,10 +1,4 @@ use super::open_ai::count_open_ai_tokens; -use crate::provider::anthropic::map_to_language_model_completion_events; -use crate::{ - settings::AllLanguageModelSettings, CloudModel, LanguageModel, LanguageModelCacheConfiguration, - LanguageModelId, LanguageModelName, LanguageModelProviderId, LanguageModelProviderName, - LanguageModelProviderState, LanguageModelRequest, RateLimiter, -}; use anthropic::AnthropicError; use anyhow::{anyhow, Result}; use client::{ @@ -22,6 +16,14 @@ use gpui::{ ModelContext, ReadGlobal, Subscription, Task, }; use http_client::{AsyncBody, HttpClient, Method, Response, StatusCode}; +use language_model::{ + CloudModel, LanguageModel, LanguageModelCacheConfiguration, LanguageModelId, LanguageModelName, + LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, + LanguageModelRequest, RateLimiter, ZED_CLOUD_PROVIDER_ID, +}; +use language_model::{ + LanguageModelAvailability, LanguageModelCompletionEvent, LanguageModelProvider, +}; use proto::TypedEnvelope; use schemars::JsonSchema; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -40,11 +42,11 @@ use strum::IntoEnumIterator; use thiserror::Error; use ui::{prelude::*, TintColor}; -use crate::{LanguageModelAvailability, LanguageModelCompletionEvent, LanguageModelProvider}; +use crate::provider::anthropic::map_to_language_model_completion_events; +use crate::AllLanguageModelSettings; use super::anthropic::count_anthropic_tokens; -pub const PROVIDER_ID: &str = "zed.dev"; pub const PROVIDER_NAME: &str = "Zed"; const ZED_CLOUD_PROVIDER_ADDITIONAL_MODELS_JSON: Option<&str> = @@ -255,7 +257,7 @@ impl LanguageModelProviderState for CloudLanguageModelProvider { impl LanguageModelProvider for CloudLanguageModelProvider { fn id(&self) -> LanguageModelProviderId { - LanguageModelProviderId(PROVIDER_ID.into()) + LanguageModelProviderId(ZED_CLOUD_PROVIDER_ID.into()) } fn name(&self) -> LanguageModelProviderName { @@ -535,7 +537,7 @@ impl LanguageModel for CloudLanguageModel { } fn provider_id(&self) -> LanguageModelProviderId { - LanguageModelProviderId(PROVIDER_ID.into()) + LanguageModelProviderId(ZED_CLOUD_PROVIDER_ID.into()) } fn provider_name(&self) -> LanguageModelProviderName { diff --git a/crates/language_model/src/provider/copilot_chat.rs b/crates/language_models/src/provider/copilot_chat.rs similarity index 98% rename from crates/language_model/src/provider/copilot_chat.rs rename to crates/language_models/src/provider/copilot_chat.rs index 0eaeaa2e3d..5ae1ad56c5 100644 --- a/crates/language_model/src/provider/copilot_chat.rs +++ b/crates/language_models/src/provider/copilot_chat.rs @@ -14,6 +14,11 @@ use gpui::{ percentage, svg, Animation, AnimationExt, AnyView, AppContext, AsyncAppContext, Model, Render, Subscription, Task, Transformation, }; +use language_model::{ + LanguageModel, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, + LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, + LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role, +}; use settings::SettingsStore; use std::time::Duration; use strum::IntoEnumIterator; @@ -23,12 +28,6 @@ use ui::{ ViewContext, VisualContext, WindowContext, }; -use crate::{ - LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProvider, - LanguageModelProviderId, LanguageModelProviderName, LanguageModelRequest, RateLimiter, Role, -}; -use crate::{LanguageModelCompletionEvent, LanguageModelProviderState}; - use super::anthropic::count_anthropic_tokens; use super::open_ai::count_open_ai_tokens; diff --git a/crates/language_model/src/provider/google.rs b/crates/language_models/src/provider/google.rs similarity index 98% rename from crates/language_model/src/provider/google.rs rename to crates/language_models/src/provider/google.rs index 94d5ffca7d..59589605ee 100644 --- a/crates/language_model/src/provider/google.rs +++ b/crates/language_models/src/provider/google.rs @@ -8,6 +8,12 @@ use gpui::{ View, WhiteSpace, }; use http_client::HttpClient; +use language_model::LanguageModelCompletionEvent; +use language_model::{ + LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProvider, + LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, + LanguageModelRequest, RateLimiter, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; @@ -17,12 +23,7 @@ use theme::ThemeSettings; use ui::{prelude::*, Icon, IconName, Tooltip}; use util::ResultExt; -use crate::LanguageModelCompletionEvent; -use crate::{ - settings::AllLanguageModelSettings, LanguageModel, LanguageModelId, LanguageModelName, - LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, - LanguageModelProviderState, LanguageModelRequest, RateLimiter, -}; +use crate::AllLanguageModelSettings; const PROVIDER_ID: &str = "google"; const PROVIDER_NAME: &str = "Google AI"; diff --git a/crates/language_model/src/provider/ollama.rs b/crates/language_models/src/provider/ollama.rs similarity index 98% rename from crates/language_model/src/provider/ollama.rs rename to crates/language_models/src/provider/ollama.rs index 3485982781..4fef43afe0 100644 --- a/crates/language_model/src/provider/ollama.rs +++ b/crates/language_models/src/provider/ollama.rs @@ -2,6 +2,12 @@ use anyhow::{anyhow, bail, Result}; use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt}; use gpui::{AnyView, AppContext, AsyncAppContext, ModelContext, Subscription, Task}; use http_client::HttpClient; +use language_model::LanguageModelCompletionEvent; +use language_model::{ + LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProvider, + LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, + LanguageModelRequest, RateLimiter, Role, +}; use ollama::{ get_models, preload_model, stream_chat_completion, ChatMessage, ChatOptions, ChatRequest, ChatResponseDelta, KeepAlive, OllamaToolCall, @@ -13,12 +19,7 @@ use std::{collections::BTreeMap, sync::Arc}; use ui::{prelude::*, ButtonLike, Indicator}; use util::ResultExt; -use crate::LanguageModelCompletionEvent; -use crate::{ - settings::AllLanguageModelSettings, LanguageModel, LanguageModelId, LanguageModelName, - LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, - LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role, -}; +use crate::AllLanguageModelSettings; const OLLAMA_DOWNLOAD_URL: &str = "https://ollama.com/download"; const OLLAMA_LIBRARY_URL: &str = "https://ollama.com/library"; diff --git a/crates/language_model/src/provider/open_ai.rs b/crates/language_models/src/provider/open_ai.rs similarity index 99% rename from crates/language_model/src/provider/open_ai.rs rename to crates/language_models/src/provider/open_ai.rs index 2a51b9a648..5c740f93e6 100644 --- a/crates/language_model/src/provider/open_ai.rs +++ b/crates/language_models/src/provider/open_ai.rs @@ -7,6 +7,11 @@ use gpui::{ View, WhiteSpace, }; use http_client::HttpClient; +use language_model::{ + LanguageModel, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, + LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, + LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role, +}; use open_ai::{ stream_completion, FunctionDefinition, ResponseStreamEvent, ToolChoice, ToolDefinition, }; @@ -19,12 +24,7 @@ use theme::ThemeSettings; use ui::{prelude::*, Icon, IconName, Tooltip}; use util::ResultExt; -use crate::LanguageModelCompletionEvent; -use crate::{ - settings::AllLanguageModelSettings, LanguageModel, LanguageModelId, LanguageModelName, - LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, - LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role, -}; +use crate::AllLanguageModelSettings; const PROVIDER_ID: &str = "openai"; const PROVIDER_NAME: &str = "OpenAI"; diff --git a/crates/language_model/src/settings.rs b/crates/language_models/src/settings.rs similarity index 97% rename from crates/language_model/src/settings.rs rename to crates/language_models/src/settings.rs index 275fcf0417..f6602427cb 100644 --- a/crates/language_model/src/settings.rs +++ b/crates/language_models/src/settings.rs @@ -2,22 +2,20 @@ use std::sync::Arc; use anyhow::Result; use gpui::AppContext; +use language_model::LanguageModelCacheConfiguration; use project::Fs; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{update_settings_file, Settings, SettingsSources}; -use crate::{ - provider::{ - self, - anthropic::AnthropicSettings, - cloud::{self, ZedDotDevSettings}, - copilot_chat::CopilotChatSettings, - google::GoogleSettings, - ollama::OllamaSettings, - open_ai::OpenAiSettings, - }, - LanguageModelCacheConfiguration, +use crate::provider::{ + self, + anthropic::AnthropicSettings, + cloud::{self, ZedDotDevSettings}, + copilot_chat::CopilotChatSettings, + google::GoogleSettings, + ollama::OllamaSettings, + open_ai::OpenAiSettings, }; /// Initializes the language model settings. diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 6f511c2951..8d12d7b9f9 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -61,6 +61,7 @@ install_cli.workspace = true journal.workspace = true language.workspace = true language_model.workspace = true +language_models.workspace = true language_selector.workspace = true language_tools.workspace = true languages = { workspace = true, features = ["load-grammars"] } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index c632843baa..9dbe00c617 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -387,7 +387,8 @@ fn main() { cx, ); supermaven::init(app_state.client.clone(), cx); - language_model::init( + language_model::init(cx); + language_models::init( app_state.user_store.clone(), app_state.client.clone(), app_state.fs.clone(), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 867ffa91e6..73ecd00192 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -3504,7 +3504,8 @@ mod tests { app_state.client.http_client().clone(), cx, ); - language_model::init( + language_model::init(cx); + language_models::init( app_state.user_store.clone(), app_state.client.clone(), app_state.fs.clone(), From 536d7e53553d339be6a964dba2af3db8586411cd Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 21 Nov 2024 01:07:14 +0100 Subject: [PATCH 082/157] chore: Sever terminal_view <-> tasks_ui dependency (#20946) Closes #ISSUE Release Notes: - N/A --- Cargo.lock | 2 +- crates/tasks_ui/Cargo.toml | 2 +- crates/tasks_ui/src/lib.rs | 11 +++-- crates/tasks_ui/src/modal.rs | 47 ++-------------------- crates/terminal_view/Cargo.toml | 1 - crates/terminal_view/src/terminal_panel.rs | 2 +- crates/terminal_view/src/terminal_view.rs | 4 +- crates/zed_actions/src/lib.rs | 40 ++++++++++++++++++ 8 files changed, 56 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a8ff3abe01..f7587449b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12196,6 +12196,7 @@ dependencies = [ "ui", "util", "workspace", + "zed_actions", ] [[package]] @@ -12299,7 +12300,6 @@ dependencies = [ "shellexpand 2.1.2", "smol", "task", - "tasks_ui", "terminal", "theme", "ui", diff --git a/crates/tasks_ui/Cargo.toml b/crates/tasks_ui/Cargo.toml index 265755319b..528d238329 100644 --- a/crates/tasks_ui/Cargo.toml +++ b/crates/tasks_ui/Cargo.toml @@ -25,7 +25,7 @@ ui.workspace = true util.workspace = true workspace.workspace = true language.workspace = true - +zed_actions.workspace = true [dev-dependencies] editor = { workspace = true, features = ["test-support"] } diff --git a/crates/tasks_ui/src/lib.rs b/crates/tasks_ui/src/lib.rs index 38b15403e2..02ced4f479 100644 --- a/crates/tasks_ui/src/lib.rs +++ b/crates/tasks_ui/src/lib.rs @@ -3,6 +3,7 @@ use editor::{tasks::task_context, Editor}; use gpui::{AppContext, Task as AsyncTask, ViewContext, WindowContext}; use modal::TasksModal; use project::{Location, WorktreeId}; +use task::TaskId; use workspace::tasks::schedule_task; use workspace::{tasks::schedule_resolved_task, Workspace}; @@ -25,9 +26,13 @@ pub fn init(cx: &mut AppContext) { .read(cx) .task_inventory() .and_then(|inventory| { - inventory - .read(cx) - .last_scheduled_task(action.task_id.as_ref()) + inventory.read(cx).last_scheduled_task( + action + .task_id + .as_ref() + .map(|id| TaskId(id.clone())) + .as_ref(), + ) }) { if action.reevaluate_context { diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 3de116702a..3c7b767d5c 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -3,13 +3,13 @@ use std::sync::Arc; use crate::active_item_selection_properties; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - impl_actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusableView, + rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusableView, InteractiveElement, Model, ParentElement, Render, SharedString, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, }; use picker::{highlighted_match_with_paths::HighlightedText, Picker, PickerDelegate}; use project::{task_store::TaskStore, TaskSourceKind}; -use task::{ResolvedTask, TaskContext, TaskId, TaskTemplate}; +use task::{ResolvedTask, TaskContext, TaskTemplate}; use ui::{ div, h_flex, v_flex, ActiveTheme, Button, ButtonCommon, ButtonSize, Clickable, Color, FluentBuilder as _, Icon, IconButton, IconButtonShape, IconName, IconSize, IntoElement, @@ -18,48 +18,7 @@ use ui::{ }; use util::ResultExt; use workspace::{tasks::schedule_resolved_task, ModalView, Workspace}; - -use serde::Deserialize; - -/// Spawn a task with name or open tasks modal -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct Spawn { - #[serde(default)] - /// Name of the task to spawn. - /// If it is not set, a modal with a list of available tasks is opened instead. - /// Defaults to None. - pub task_name: Option, -} - -impl Spawn { - pub fn modal() -> Self { - Self { task_name: None } - } -} - -/// Rerun last task -#[derive(PartialEq, Clone, Deserialize, Default)] -pub struct Rerun { - /// Controls whether the task context is reevaluated prior to execution of a task. - /// If it is not, environment variables such as ZED_COLUMN, ZED_FILE are gonna be the same as in the last execution of a task - /// If it is, these variables will be updated to reflect current state of editor at the time task::Rerun is executed. - /// default: false - #[serde(default)] - pub reevaluate_context: bool, - /// Overrides `allow_concurrent_runs` property of the task being reran. - /// Default: null - #[serde(default)] - pub allow_concurrent_runs: Option, - /// Overrides `use_new_terminal` property of the task being reran. - /// Default: null - #[serde(default)] - pub use_new_terminal: Option, - - /// If present, rerun the task with this ID, otherwise rerun the last task. - pub task_id: Option, -} - -impl_actions!(task, [Rerun, Spawn]); +pub use zed_actions::{Rerun, Spawn}; /// A modal used to spawn new tasks. pub(crate) struct TasksModalDelegate { diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index 09b0b0d2d5..64b979cdd6 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -24,7 +24,6 @@ itertools.workspace = true language.workspace = true project.workspace = true task.workspace = true -tasks_ui.workspace = true search.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 6d64ac1a48..2ca7561bdb 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -218,7 +218,7 @@ impl TerminalPanel { // context menu will be gone the moment we spawn the modal. .action( "Spawn task", - tasks_ui::Spawn::modal().boxed_clone(), + zed_actions::Spawn::modal().boxed_clone(), ) }); diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 6a23e45f54..21d20599b9 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1044,8 +1044,8 @@ impl Item for TerminalView { .shape(ui::IconButtonShape::Square) .tooltip(|cx| Tooltip::text("Rerun task", cx)) .on_click(move |_, cx| { - cx.dispatch_action(Box::new(tasks_ui::Rerun { - task_id: Some(task_id.clone()), + cx.dispatch_action(Box::new(zed_actions::Rerun { + task_id: Some(task_id.0.clone()), allow_concurrent_runs: Some(true), use_new_terminal: Some(false), reevaluate_context: false, diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 2f33583429..53f5b202a8 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -58,3 +58,43 @@ pub struct OpenRecent { } gpui::impl_actions!(projects, [OpenRecent]); gpui::actions!(projects, [OpenRemote]); + +/// Spawn a task with name or open tasks modal +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct Spawn { + #[serde(default)] + /// Name of the task to spawn. + /// If it is not set, a modal with a list of available tasks is opened instead. + /// Defaults to None. + pub task_name: Option, +} + +impl Spawn { + pub fn modal() -> Self { + Self { task_name: None } + } +} + +/// Rerun last task +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct Rerun { + /// Controls whether the task context is reevaluated prior to execution of a task. + /// If it is not, environment variables such as ZED_COLUMN, ZED_FILE are gonna be the same as in the last execution of a task + /// If it is, these variables will be updated to reflect current state of editor at the time task::Rerun is executed. + /// default: false + #[serde(default)] + pub reevaluate_context: bool, + /// Overrides `allow_concurrent_runs` property of the task being reran. + /// Default: null + #[serde(default)] + pub allow_concurrent_runs: Option, + /// Overrides `use_new_terminal` property of the task being reran. + /// Default: null + #[serde(default)] + pub use_new_terminal: Option, + + /// If present, rerun the task with this ID, otherwise rerun the last task. + pub task_id: Option, +} + +impl_actions!(task, [Spawn, Rerun]); From 33e84da657f279de1eb549eb351a5e026b608c3d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 19:39:49 -0500 Subject: [PATCH 083/157] Update Rust crate cargo_metadata to 0.19 (#20948) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [cargo_metadata](https://redirect.github.com/oli-obk/cargo_metadata) | workspace.dependencies | minor | `0.18` -> `0.19` | --- ### Release Notes
oli-obk/cargo_metadata (cargo_metadata) ### [`v0.19.0`](https://redirect.github.com/oli-obk/cargo_metadata/blob/HEAD/CHANGELOG.md#0190---2024-11-20) [Compare Source](https://redirect.github.com/oli-obk/cargo_metadata/compare/0.18.1...0.19.0) ##### Added - Re-exported `semver` crate directly. - Added implementation of `std::ops::Index<&PackageId>` for `Resolve`. - Added `pub fn is_kind(&self, name: TargetKind) -> bool` to `Target`. - Added derived implementations of `PartialEq`, `Eq` and `Hash` for `Metadata` and its members' types. - Added default fields to `PackageBuilder`. - Added `pub fn new(name:version:id:path:) -> Self` to `PackageBuilder` for providing all required fields upfront. ##### Changed - Bumped MSRV from `1.42.0` to `1.56.0`. - Made `parse_stream` more versatile by accepting anything that implements `Read`. - Converted `TargetKind` and `CrateType` to an enum representation. ##### Removed - Removed re-exports for `BuildMetadata` and `Prerelease` from `semver` crate. - Removed `.is_lib(…)`, `.is_bin(…)`, `.is_example(…)`, `.is_test(…)`, `.is_bench(…)`, `.is_custom_build(…)`, and `.is_proc_macro(…)` from `Target` (in favor of adding `.is_kind(…)`). ##### Fixed - Added missing `manifest_path` field to `Artifact`. Fixes [#​187](https://redirect.github.com/oli-obk/cargo_metadata/issues/187).
--- ### Configuration 📅 **Schedule**: Branch creation - "after 3pm on Wednesday" in timezone America/New_York, Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- Release Notes: - N/A Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7587449b1..f73dd6b8c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2108,9 +2108,9 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +checksum = "afc309ed89476c8957c50fb818f56fe894db857866c3e163335faa91dc34eb85" dependencies = [ "camino", "cargo-platform", diff --git a/Cargo.toml b/Cargo.toml index 8357160268..a5555864d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -336,7 +336,7 @@ blade-macros = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a blade-util = { git = "https://github.com/kvark/blade", rev = "e142a3a5e678eb6a13e642ad8401b1f3aa38e969" } blake3 = "1.5.3" bytes = "1.0" -cargo_metadata = "0.18" +cargo_metadata = "0.19" cargo_toml = "0.20" chrono = { version = "0.4", features = ["serde"] } clap = { version = "4.4", features = ["derive"] } From 49ed932c1f2cd5b293b35fbdbc279550cc542ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Thu, 21 Nov 2024 08:47:55 +0800 Subject: [PATCH 084/157] Fix line truncate crash on Windows (#17271) Closes #17267 We should update the `len` of `runs` when truncating. cc @huacnlee Release Notes: - N/A --- crates/gpui/src/elements/text.rs | 4 +- crates/gpui/src/text_system/line_wrapper.rs | 197 +++++++++++++++++--- 2 files changed, 174 insertions(+), 27 deletions(-) diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 56b551737a..427097d1b7 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -263,7 +263,7 @@ impl TextLayout { .line_height .to_pixels(font_size.into(), cx.rem_size()); - let runs = if let Some(runs) = runs { + let mut runs = if let Some(runs) = runs { runs } else { vec![text_style.to_run(text.len())] @@ -306,7 +306,7 @@ impl TextLayout { let mut line_wrapper = cx.text_system().line_wrapper(text_style.font(), font_size); let text = if let Some(truncate_width) = truncate_width { - line_wrapper.truncate_line(text.clone(), truncate_width, ellipsis) + line_wrapper.truncate_line(text.clone(), truncate_width, ellipsis, &mut runs) } else { text.clone() }; diff --git a/crates/gpui/src/text_system/line_wrapper.rs b/crates/gpui/src/text_system/line_wrapper.rs index 3d38ca315c..1b99165eee 100644 --- a/crates/gpui/src/text_system/line_wrapper.rs +++ b/crates/gpui/src/text_system/line_wrapper.rs @@ -1,4 +1,4 @@ -use crate::{px, FontId, FontRun, Pixels, PlatformTextSystem, SharedString}; +use crate::{px, FontId, FontRun, Pixels, PlatformTextSystem, SharedString, TextRun}; use collections::HashMap; use std::{iter, sync::Arc}; @@ -104,6 +104,7 @@ impl LineWrapper { line: SharedString, truncate_width: Pixels, ellipsis: Option<&str>, + runs: &mut Vec, ) -> SharedString { let mut width = px(0.); let mut ellipsis_width = px(0.); @@ -124,15 +125,15 @@ impl LineWrapper { width += char_width; if width.floor() > truncate_width { - return SharedString::from(format!( - "{}{}", - &line[..truncate_ix], - ellipsis.unwrap_or("") - )); + let ellipsis = ellipsis.unwrap_or(""); + let result = SharedString::from(format!("{}{}", &line[..truncate_ix], ellipsis)); + update_runs_after_truncation(&result, ellipsis, runs); + + return result; } } - line.clone() + line } pub(crate) fn is_word_char(c: char) -> bool { @@ -195,6 +196,23 @@ impl LineWrapper { } } +fn update_runs_after_truncation(result: &str, ellipsis: &str, runs: &mut Vec) { + let mut truncate_at = result.len() - ellipsis.len(); + let mut run_end = None; + for (run_index, run) in runs.iter_mut().enumerate() { + if run.len <= truncate_at { + truncate_at -= run.len; + } else { + run.len = truncate_at + ellipsis.len(); + run_end = Some(run_index + 1); + break; + } + } + if let Some(run_end) = run_end { + runs.truncate(run_end); + } +} + /// A boundary between two lines of text. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Boundary { @@ -213,7 +231,9 @@ impl Boundary { #[cfg(test)] mod tests { use super::*; - use crate::{font, TestAppContext, TestDispatcher}; + use crate::{ + font, Font, FontFeatures, FontStyle, FontWeight, Hsla, TestAppContext, TestDispatcher, + }; #[cfg(target_os = "macos")] use crate::{TextRun, WindowTextSystem, WrapBoundary}; use rand::prelude::*; @@ -232,6 +252,26 @@ mod tests { LineWrapper::new(id, px(16.), cx.text_system().platform_text_system.clone()) } + fn generate_test_runs(input_run_len: &[usize]) -> Vec { + input_run_len + .iter() + .map(|run_len| TextRun { + len: *run_len, + font: Font { + family: "Dummy".into(), + features: FontFeatures::default(), + fallbacks: None, + weight: FontWeight::default(), + style: FontStyle::Normal, + }, + color: Hsla::default(), + background_color: None, + underline: None, + strikethrough: None, + }) + .collect() + } + #[test] fn test_wrap_line() { let mut wrapper = build_wrapper(); @@ -293,28 +333,135 @@ mod tests { fn test_truncate_line() { let mut wrapper = build_wrapper(); - assert_eq!( - wrapper.truncate_line("aa bbb cccc ddddd eeee ffff gggg".into(), px(220.), None), - "aa bbb cccc ddddd eeee" + fn perform_test( + wrapper: &mut LineWrapper, + text: &'static str, + result: &'static str, + ellipsis: Option<&str>, + ) { + let dummy_run_lens = vec![text.len()]; + let mut dummy_runs = generate_test_runs(&dummy_run_lens); + assert_eq!( + wrapper.truncate_line(text.into(), px(220.), ellipsis, &mut dummy_runs), + result + ); + assert_eq!(dummy_runs.first().unwrap().len, result.len()); + } + + perform_test( + &mut wrapper, + "aa bbb cccc ddddd eeee ffff gggg", + "aa bbb cccc ddddd eeee", + None, ); - assert_eq!( - wrapper.truncate_line( - "aa bbb cccc ddddd eeee ffff gggg".into(), - px(220.), - Some("…") - ), - "aa bbb cccc ddddd eee…" + perform_test( + &mut wrapper, + "aa bbb cccc ddddd eeee ffff gggg", + "aa bbb cccc ddddd eee…", + Some("…"), ); - assert_eq!( - wrapper.truncate_line( - "aa bbb cccc ddddd eeee ffff gggg".into(), - px(220.), - Some("......") - ), - "aa bbb cccc dddd......" + perform_test( + &mut wrapper, + "aa bbb cccc ddddd eeee ffff gggg", + "aa bbb cccc dddd......", + Some("......"), ); } + #[test] + fn test_truncate_multiple_runs() { + let mut wrapper = build_wrapper(); + + fn perform_test( + wrapper: &mut LineWrapper, + text: &'static str, + result: &str, + run_lens: &[usize], + result_run_len: &[usize], + line_width: Pixels, + ) { + let mut dummy_runs = generate_test_runs(run_lens); + assert_eq!( + wrapper.truncate_line(text.into(), line_width, Some("…"), &mut dummy_runs), + result + ); + for (run, result_len) in dummy_runs.iter().zip(result_run_len) { + assert_eq!(run.len, *result_len); + } + } + // Case 0: Normal + // Text: abcdefghijkl + // Runs: Run0 { len: 12, ... } + // + // Truncate res: abcd… (truncate_at = 4) + // Run res: Run0 { string: abcd…, len: 7, ... } + perform_test(&mut wrapper, "abcdefghijkl", "abcd…", &[12], &[7], px(50.)); + // Case 1: Drop some runs + // Text: abcdefghijkl + // Runs: Run0 { len: 4, ... }, Run1 { len: 4, ... }, Run2 { len: 4, ... } + // + // Truncate res: abcdef… (truncate_at = 6) + // Runs res: Run0 { string: abcd, len: 4, ... }, Run1 { string: ef…, len: + // 5, ... } + perform_test( + &mut wrapper, + "abcdefghijkl", + "abcdef…", + &[4, 4, 4], + &[4, 5], + px(70.), + ); + // Case 2: Truncate at start of some run + // Text: abcdefghijkl + // Runs: Run0 { len: 4, ... }, Run1 { len: 4, ... }, Run2 { len: 4, ... } + // + // Truncate res: abcdefgh… (truncate_at = 8) + // Runs res: Run0 { string: abcd, len: 4, ... }, Run1 { string: efgh, len: + // 4, ... }, Run2 { string: …, len: 3, ... } + perform_test( + &mut wrapper, + "abcdefghijkl", + "abcdefgh…", + &[4, 4, 4], + &[4, 4, 3], + px(90.), + ); + } + + #[test] + fn test_update_run_after_truncation() { + fn perform_test(result: &str, run_lens: &[usize], result_run_lens: &[usize]) { + let mut dummy_runs = generate_test_runs(run_lens); + update_runs_after_truncation(result, "…", &mut dummy_runs); + for (run, result_len) in dummy_runs.iter().zip(result_run_lens) { + assert_eq!(run.len, *result_len); + } + } + // Case 0: Normal + // Text: abcdefghijkl + // Runs: Run0 { len: 12, ... } + // + // Truncate res: abcd… (truncate_at = 4) + // Run res: Run0 { string: abcd…, len: 7, ... } + perform_test("abcd…", &[12], &[7]); + // Case 1: Drop some runs + // Text: abcdefghijkl + // Runs: Run0 { len: 4, ... }, Run1 { len: 4, ... }, Run2 { len: 4, ... } + // + // Truncate res: abcdef… (truncate_at = 6) + // Runs res: Run0 { string: abcd, len: 4, ... }, Run1 { string: ef…, len: + // 5, ... } + perform_test("abcdef…", &[4, 4, 4], &[4, 5]); + // Case 2: Truncate at start of some run + // Text: abcdefghijkl + // Runs: Run0 { len: 4, ... }, Run1 { len: 4, ... }, Run2 { len: 4, ... } + // + // Truncate res: abcdefgh… (truncate_at = 8) + // Runs res: Run0 { string: abcd, len: 4, ... }, Run1 { string: efgh, len: + // 4, ... }, Run2 { string: …, len: 3, ... } + perform_test("abcdefgh…", &[4, 4, 4], &[4, 4, 3]); + } + #[test] fn test_is_word_char() { #[track_caller] From 95ace0370672b4784821648887d57a95bf974291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Thu, 21 Nov 2024 08:52:38 +0800 Subject: [PATCH 085/157] windows: Set `CREATE_NO_WINDOW` for commands (#18447) - Closes: #18371 Release Notes: - N/A --- Cargo.lock | 9 ++---- crates/context_servers/src/client.rs | 4 +-- crates/evals/Cargo.toml | 3 +- crates/evals/src/eval.rs | 10 +++--- crates/extension/Cargo.toml | 1 + crates/extension/src/extension_builder.rs | 20 ++++++------ crates/git/Cargo.toml | 4 --- crates/git/src/blame.rs | 16 ++-------- crates/git/src/commit.rs | 15 ++------- crates/git/src/status.rs | 16 ++-------- crates/gpui/src/platform/windows/platform.rs | 2 +- crates/languages/Cargo.toml | 2 +- crates/languages/src/c.rs | 2 +- crates/languages/src/go.rs | 8 ++--- crates/languages/src/python.rs | 10 +++--- crates/languages/src/rust.rs | 8 ++--- crates/lsp/Cargo.toml | 3 -- crates/lsp/src/lsp.rs | 25 ++++++--------- crates/node_runtime/Cargo.toml | 1 - crates/node_runtime/src/node_runtime.rs | 31 ++++++++----------- crates/project/Cargo.toml | 3 -- crates/project/src/lsp_store.rs | 11 ++----- crates/remote/src/ssh_session.rs | 8 ++--- crates/repl/Cargo.toml | 3 -- crates/repl/src/kernels/mod.rs | 3 +- crates/repl/src/kernels/native_kernel.rs | 24 ++++----------- crates/supermaven/Cargo.toml | 3 -- crates/supermaven/src/supermaven.rs | 17 +++-------- crates/util/Cargo.toml | 1 + crates/util/src/command.rs | 32 ++++++++++++++++++++ crates/util/src/util.rs | 1 + 31 files changed, 122 insertions(+), 174 deletions(-) create mode 100644 crates/util/src/command.rs diff --git a/Cargo.lock b/Cargo.lock index f73dd6b8c5..8db4b8424d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4030,6 +4030,7 @@ dependencies = [ "serde_json", "settings", "smol", + "util", ] [[package]] @@ -4115,6 +4116,7 @@ dependencies = [ "serde", "serde_json", "toml 0.8.19", + "util", "wasm-encoder 0.215.0", "wasmparser 0.215.0", "wit-component", @@ -4936,7 +4938,6 @@ dependencies = [ "unindent", "url", "util", - "windows 0.58.0", ] [[package]] @@ -6951,7 +6952,6 @@ dependencies = [ "serde_json", "smol", "util", - "windows 0.58.0", ] [[package]] @@ -7490,7 +7490,6 @@ dependencies = [ "util", "walkdir", "which 6.0.3", - "windows 0.58.0", ] [[package]] @@ -9179,7 +9178,6 @@ dependencies = [ "url", "util", "which 6.0.3", - "windows 0.58.0", "worktree", ] @@ -9941,7 +9939,6 @@ dependencies = [ "ui", "util", "uuid", - "windows 0.58.0", "workspace", ] @@ -11829,7 +11826,6 @@ dependencies = [ "ui", "unicode-segmentation", "util", - "windows 0.58.0", ] [[package]] @@ -13548,6 +13544,7 @@ dependencies = [ "rust-embed", "serde", "serde_json", + "smol", "take-until", "tempfile", "tendril", diff --git a/crates/context_servers/src/client.rs b/crates/context_servers/src/client.rs index 8202e950d6..64aabb00e8 100644 --- a/crates/context_servers/src/client.rs +++ b/crates/context_servers/src/client.rs @@ -9,7 +9,7 @@ use serde_json::{value::RawValue, Value}; use smol::{ channel, io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, - process::{self, Child}, + process::Child, }; use std::{ fmt, @@ -152,7 +152,7 @@ impl Client { &binary.args ); - let mut command = process::Command::new(&binary.executable); + let mut command = util::command::new_smol_command(&binary.executable); command .args(&binary.args) .envs(binary.env.unwrap_or_default()) diff --git a/crates/evals/Cargo.toml b/crates/evals/Cargo.toml index 3057edcd1a..744094aeaf 100644 --- a/crates/evals/Cargo.toml +++ b/crates/evals/Cargo.toml @@ -30,9 +30,10 @@ languages.workspace = true node_runtime.workspace = true open_ai.workspace = true project.workspace = true +reqwest_client.workspace = true semantic_index.workspace = true serde.workspace = true serde_json.workspace = true settings.workspace = true smol.workspace = true -reqwest_client.workspace = true +util.workspace = true diff --git a/crates/evals/src/eval.rs b/crates/evals/src/eval.rs index 2db13ff392..67b73a835b 100644 --- a/crates/evals/src/eval.rs +++ b/crates/evals/src/eval.rs @@ -27,7 +27,7 @@ use std::time::Duration; use std::{ fs, path::Path, - process::{exit, Command, Stdio}, + process::{exit, Stdio}, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, @@ -667,7 +667,7 @@ async fn fetch_eval_repo( return; } if !repo_dir.join(".git").exists() { - let init_output = Command::new("git") + let init_output = util::command::new_std_command("git") .current_dir(&repo_dir) .args(&["init"]) .output() @@ -682,13 +682,13 @@ async fn fetch_eval_repo( } } let url = format!("https://github.com/{}.git", repo); - Command::new("git") + util::command::new_std_command("git") .current_dir(&repo_dir) .args(&["remote", "add", "-f", "origin", &url]) .stdin(Stdio::null()) .output() .unwrap(); - let fetch_output = Command::new("git") + let fetch_output = util::command::new_std_command("git") .current_dir(&repo_dir) .args(&["fetch", "--depth", "1", "origin", &sha]) .stdin(Stdio::null()) @@ -703,7 +703,7 @@ async fn fetch_eval_repo( ); return; } - let checkout_output = Command::new("git") + let checkout_output = util::command::new_std_command("git") .current_dir(&repo_dir) .args(&["checkout", &sha]) .output() diff --git a/crates/extension/Cargo.toml b/crates/extension/Cargo.toml index b4d23fd709..a96cf7155a 100644 --- a/crates/extension/Cargo.toml +++ b/crates/extension/Cargo.toml @@ -28,6 +28,7 @@ semantic_version.workspace = true serde.workspace = true serde_json.workspace = true toml.workspace = true +util.workspace = true wasm-encoder.workspace = true wasmparser.workspace = true wit-component.workspace = true diff --git a/crates/extension/src/extension_builder.rs b/crates/extension/src/extension_builder.rs index 25e6a1a485..a2d7ae573f 100644 --- a/crates/extension/src/extension_builder.rs +++ b/crates/extension/src/extension_builder.rs @@ -11,7 +11,7 @@ use serde::Deserialize; use std::{ env, fs, mem, path::{Path, PathBuf}, - process::{Command, Stdio}, + process::Stdio, sync::Arc, }; use wasm_encoder::{ComponentSectionId, Encode as _, RawSection, Section as _}; @@ -130,7 +130,7 @@ impl ExtensionBuilder { "compiling Rust crate for extension {}", extension_dir.display() ); - let output = Command::new("cargo") + let output = util::command::new_std_command("cargo") .args(["build", "--target", RUST_TARGET]) .args(options.release.then_some("--release")) .arg("--target-dir") @@ -237,7 +237,7 @@ impl ExtensionBuilder { let scanner_path = src_path.join("scanner.c"); log::info!("compiling {grammar_name} parser"); - let clang_output = Command::new(&clang_path) + let clang_output = util::command::new_std_command(&clang_path) .args(["-fPIC", "-shared", "-Os"]) .arg(format!("-Wl,--export=tree_sitter_{grammar_name}")) .arg("-o") @@ -264,7 +264,7 @@ impl ExtensionBuilder { let git_dir = directory.join(".git"); if directory.exists() { - let remotes_output = Command::new("git") + let remotes_output = util::command::new_std_command("git") .arg("--git-dir") .arg(&git_dir) .args(["remote", "-v"]) @@ -287,7 +287,7 @@ impl ExtensionBuilder { fs::create_dir_all(directory).with_context(|| { format!("failed to create grammar directory {}", directory.display(),) })?; - let init_output = Command::new("git") + let init_output = util::command::new_std_command("git") .arg("init") .current_dir(directory) .output()?; @@ -298,7 +298,7 @@ impl ExtensionBuilder { ); } - let remote_add_output = Command::new("git") + let remote_add_output = util::command::new_std_command("git") .arg("--git-dir") .arg(&git_dir) .args(["remote", "add", "origin", url]) @@ -312,14 +312,14 @@ impl ExtensionBuilder { } } - let fetch_output = Command::new("git") + let fetch_output = util::command::new_std_command("git") .arg("--git-dir") .arg(&git_dir) .args(["fetch", "--depth", "1", "origin", rev]) .output() .context("failed to execute `git fetch`")?; - let checkout_output = Command::new("git") + let checkout_output = util::command::new_std_command("git") .arg("--git-dir") .arg(&git_dir) .args(["checkout", rev]) @@ -346,7 +346,7 @@ impl ExtensionBuilder { } fn install_rust_wasm_target_if_needed(&self) -> Result<()> { - let rustc_output = Command::new("rustc") + let rustc_output = util::command::new_std_command("rustc") .arg("--print") .arg("sysroot") .output() @@ -363,7 +363,7 @@ impl ExtensionBuilder { return Ok(()); } - let output = Command::new("rustup") + let output = util::command::new_std_command("rustup") .args(["target", "add", RUST_TARGET]) .stderr(Stdio::piped()) .stdout(Stdio::inherit()) diff --git a/crates/git/Cargo.toml b/crates/git/Cargo.toml index 06a46b3b76..8723e41ce4 100644 --- a/crates/git/Cargo.toml +++ b/crates/git/Cargo.toml @@ -31,10 +31,6 @@ time.workspace = true url.workspace = true util.workspace = true -[target.'cfg(target_os = "windows")'.dependencies] -windows.workspace = true - - [dev-dependencies] unindent.workspace = true serde_json.workspace = true diff --git a/crates/git/src/blame.rs b/crates/git/src/blame.rs index 030309df96..8f87a8ca54 100644 --- a/crates/git/src/blame.rs +++ b/crates/git/src/blame.rs @@ -4,7 +4,7 @@ use anyhow::{anyhow, Context, Result}; use collections::{HashMap, HashSet}; use serde::{Deserialize, Serialize}; use std::io::Write; -use std::process::{Command, Stdio}; +use std::process::Stdio; use std::sync::Arc; use std::{ops::Range, path::Path}; use text::Rope; @@ -80,9 +80,7 @@ fn run_git_blame( path: &Path, contents: &Rope, ) -> Result { - let mut child = Command::new(git_binary); - - child + let child = util::command::new_std_command(git_binary) .current_dir(working_directory) .arg("blame") .arg("--incremental") @@ -91,15 +89,7 @@ fn run_git_blame( .arg(path.as_os_str()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - - #[cfg(windows)] - { - use std::os::windows::process::CommandExt; - child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0); - } - - let child = child + .stderr(Stdio::piped()) .spawn() .map_err(|e| anyhow!("Failed to start git blame process: {}", e))?; diff --git a/crates/git/src/commit.rs b/crates/git/src/commit.rs index bdac6ff287..f32ad226af 100644 --- a/crates/git/src/commit.rs +++ b/crates/git/src/commit.rs @@ -2,10 +2,6 @@ use crate::Oid; use anyhow::{anyhow, Result}; use collections::HashMap; use std::path::Path; -use std::process::Command; - -#[cfg(windows)] -use std::os::windows::process::CommandExt; pub fn get_messages(working_directory: &Path, shas: &[Oid]) -> Result> { if shas.is_empty() { @@ -14,19 +10,12 @@ pub fn get_messages(working_directory: &Path, shas: &[Oid]) -> Result Result { - let mut child = Command::new(git_binary); - - child + let child = util::command::new_std_command(git_binary) .current_dir(working_directory) .args([ "--no-optional-locks", @@ -37,15 +35,7 @@ impl GitStatus { })) .stdin(Stdio::null()) .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - - #[cfg(windows)] - { - use std::os::windows::process::CommandExt; - child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0); - } - - let child = child + .stderr(Stdio::piped()) .spawn() .map_err(|e| anyhow!("Failed to start git status process: {}", e))?; diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 29443afabb..91e9816106 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -292,7 +292,7 @@ impl Platform for WindowsPlatform { pid, app_path.display(), ); - let restart_process = std::process::Command::new("powershell.exe") + let restart_process = util::command::new_std_command("powershell.exe") .arg("-command") .arg(script) .spawn(); diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index 96a44403bc..951423056e 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -29,7 +29,7 @@ load-grammars = [ "tree-sitter-rust", "tree-sitter-typescript", "tree-sitter-yaml", - "tree-sitter" + "tree-sitter", ] [dependencies] diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index a0e0f6dadb..5bfb7f0bc2 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -85,7 +85,7 @@ impl super::LspAdapter for CLspAdapter { } futures::io::copy(response.body_mut(), &mut file).await?; - let unzip_status = smol::process::Command::new("unzip") + let unzip_status = util::command::new_smol_command("unzip") .current_dir(&container_dir) .arg(&zip_path) .output() diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index 669f6918a9..64583ad61f 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -8,7 +8,7 @@ pub use language::*; use lsp::{LanguageServerBinary, LanguageServerName}; use regex::Regex; use serde_json::json; -use smol::{fs, process}; +use smol::fs; use std::{ any::Any, borrow::Cow, @@ -138,8 +138,8 @@ impl super::LspAdapter for GoLspAdapter { let gobin_dir = container_dir.join("gobin"); fs::create_dir_all(&gobin_dir).await?; - let go = delegate.which("go".as_ref()).await.unwrap_or("go".into()); - let install_output = process::Command::new(go) + + let install_output = util::command::new_smol_command("go") .env("GO111MODULE", "on") .env("GOBIN", &gobin_dir) .args(["install", "golang.org/x/tools/gopls@latest"]) @@ -157,7 +157,7 @@ impl super::LspAdapter for GoLspAdapter { } let installed_binary_path = gobin_dir.join("gopls"); - let version_output = process::Command::new(&installed_binary_path) + let version_output = util::command::new_smol_command(&installed_binary_path) .arg("version") .output() .await diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index a29eb1c679..a5fe479627 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -19,7 +19,7 @@ use pet_core::python_environment::PythonEnvironmentKind; use pet_core::Configuration; use project::lsp_store::language_server_settings; use serde_json::{json, Value}; -use smol::{lock::OnceCell, process::Command}; +use smol::lock::OnceCell; use std::cmp::Ordering; use std::str::FromStr; @@ -698,7 +698,7 @@ impl PyLspAdapter { let mut path = PathBuf::from(work_dir.as_ref()); path.push("pylsp-venv"); if !path.exists() { - Command::new(python_path) + util::command::new_smol_command(python_path) .arg("-m") .arg("venv") .arg("pylsp-venv") @@ -779,7 +779,7 @@ impl LspAdapter for PyLspAdapter { let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?; let pip_path = venv.join("bin").join("pip3"); ensure!( - Command::new(pip_path.as_path()) + util::command::new_smol_command(pip_path.as_path()) .arg("install") .arg("python-lsp-server") .output() @@ -789,7 +789,7 @@ impl LspAdapter for PyLspAdapter { "python-lsp-server installation failed" ); ensure!( - Command::new(pip_path.as_path()) + util::command::new_smol_command(pip_path.as_path()) .arg("install") .arg("python-lsp-server[all]") .output() @@ -799,7 +799,7 @@ impl LspAdapter for PyLspAdapter { "python-lsp-server[all] installation failed" ); ensure!( - Command::new(pip_path) + util::command::new_smol_command(pip_path) .arg("install") .arg("pylsp-mypy") .output() diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index 730f20b134..7f5912d73e 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -14,8 +14,7 @@ use std::{ any::Any, borrow::Cow, path::{Path, PathBuf}, - sync::Arc, - sync::LazyLock, + sync::{Arc, LazyLock}, }; use task::{TaskTemplate, TaskTemplates, TaskVariables, VariableName}; use util::{fs::remove_matching, maybe, ResultExt}; @@ -639,7 +638,7 @@ fn package_name_and_bin_name_from_abs_path( abs_path: &Path, project_env: Option<&HashMap>, ) -> Option<(String, String)> { - let mut command = std::process::Command::new("cargo"); + let mut command = util::command::new_std_command("cargo"); if let Some(envs) = project_env { command.envs(envs); } @@ -685,11 +684,10 @@ fn human_readable_package_name( package_directory: &Path, project_env: Option<&HashMap>, ) -> Option { - let mut command = std::process::Command::new("cargo"); + let mut command = util::command::new_std_command("cargo"); if let Some(envs) = project_env { command.envs(envs); } - let pkgid = String::from_utf8( command .current_dir(package_directory) diff --git a/crates/lsp/Cargo.toml b/crates/lsp/Cargo.toml index 3460bf34dd..f06173ac1b 100644 --- a/crates/lsp/Cargo.toml +++ b/crates/lsp/Cargo.toml @@ -32,9 +32,6 @@ smol.workspace = true util.workspace = true release_channel.workspace = true -[target.'cfg(windows)'.dependencies] -windows.workspace = true - [dev-dependencies] async-pipe.workspace = true ctor.workspace = true diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 5f0186e61e..87c04030bd 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -19,12 +19,9 @@ use serde_json::{json, value::RawValue, Value}; use smol::{ channel, io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, - process::{self, Child}, + process::Child, }; -#[cfg(target_os = "windows")] -use smol::process::windows::CommandExt; - use std::{ ffi::{OsStr, OsString}, fmt, @@ -346,23 +343,21 @@ impl LanguageServer { &binary.arguments ); - let mut command = process::Command::new(&binary.path); - command + let mut server = util::command::new_smol_command(&binary.path) .current_dir(working_dir) .args(&binary.arguments) .envs(binary.env.unwrap_or_default()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .kill_on_drop(true); - #[cfg(windows)] - command.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0); - let mut server = command.spawn().with_context(|| { - format!( - "failed to spawn command. path: {:?}, working directory: {:?}, args: {:?}", - binary.path, working_dir, &binary.arguments - ) - })?; + .kill_on_drop(true) + .spawn() + .with_context(|| { + format!( + "failed to spawn command. path: {:?}, working directory: {:?}, args: {:?}", + binary.path, working_dir, &binary.arguments + ) + })?; let stdin = server.stdin.take().unwrap(); let stdout = server.stdout.take().unwrap(); diff --git a/crates/node_runtime/Cargo.toml b/crates/node_runtime/Cargo.toml index d852b7ebdf..20b6be407f 100644 --- a/crates/node_runtime/Cargo.toml +++ b/crates/node_runtime/Cargo.toml @@ -37,7 +37,6 @@ which.workspace = true [target.'cfg(windows)'.dependencies] async-std = { version = "1.12.0", features = ["unstable"] } -windows.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 9ad14bddc4..33df4f7d15 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -9,7 +9,7 @@ use http_client::{HttpClient, Uri}; use semver::Version; use serde::Deserialize; use smol::io::BufReader; -use smol::{fs, lock::Mutex, process::Command}; +use smol::{fs, lock::Mutex}; use std::ffi::OsString; use std::io; use std::process::{Output, Stdio}; @@ -20,9 +20,6 @@ use std::{ }; use util::ResultExt; -#[cfg(windows)] -use smol::process::windows::CommandExt; - #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct NodeBinaryOptions { pub allow_path_lookup: bool, @@ -315,9 +312,7 @@ impl ManagedNodeRuntime { let node_binary = node_dir.join(Self::NODE_PATH); let npm_file = node_dir.join(Self::NPM_PATH); - let mut command = Command::new(&node_binary); - - command + let result = util::command::new_smol_command(&node_binary) .env_clear() .arg(npm_file) .arg("--version") @@ -326,12 +321,9 @@ impl ManagedNodeRuntime { .stderr(Stdio::null()) .args(["--cache".into(), node_dir.join("cache")]) .args(["--userconfig".into(), node_dir.join("blank_user_npmrc")]) - .args(["--globalconfig".into(), node_dir.join("blank_global_npmrc")]); - - #[cfg(windows)] - command.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0); - - let result = command.status().await; + .args(["--globalconfig".into(), node_dir.join("blank_global_npmrc")]) + .status() + .await; let valid = matches!(result, Ok(status) if status.success()); if !valid { @@ -412,7 +404,7 @@ impl NodeRuntimeTrait for ManagedNodeRuntime { return Err(anyhow!("missing npm file")); } - let mut command = Command::new(node_binary); + let mut command = util::command::new_smol_command(node_binary); command.env_clear(); command.env("PATH", env_path); command.arg(npm_file).arg(subcommand); @@ -473,7 +465,7 @@ pub struct SystemNodeRuntime { impl SystemNodeRuntime { const MIN_VERSION: semver::Version = Version::new(18, 0, 0); async fn new(node: PathBuf, npm: PathBuf) -> Result> { - let output = Command::new(&node) + let output = util::command::new_smol_command(&node) .arg("--version") .output() .await @@ -543,7 +535,7 @@ impl NodeRuntimeTrait for SystemNodeRuntime { subcommand: &str, args: &[&str], ) -> anyhow::Result { - let mut command = Command::new(self.npm.clone()); + let mut command = util::command::new_smol_command(self.npm.clone()); command .env_clear() .env("PATH", std::env::var_os("PATH").unwrap_or_default()) @@ -639,7 +631,11 @@ impl NodeRuntimeTrait for UnavailableNodeRuntime { } } -fn configure_npm_command(command: &mut Command, directory: Option<&Path>, proxy: Option<&Uri>) { +fn configure_npm_command( + command: &mut smol::process::Command, + directory: Option<&Path>, + proxy: Option<&Uri>, +) { if let Some(directory) = directory { command.current_dir(directory); command.args(["--prefix".into(), directory.to_path_buf()]); @@ -674,6 +670,5 @@ fn configure_npm_command(command: &mut Command, directory: Option<&Path>, proxy: { command.env("ComSpec", val); } - command.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0); } } diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index b9fdd04be6..68fdb375f4 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -73,9 +73,6 @@ url.workspace = true which.workspace = true fancy-regex.workspace = true -[target.'cfg(target_os = "windows")'.dependencies] -windows.workspace = true - [dev-dependencies] client = { workspace = true, features = ["test-support"] } collections = { workspace = true, features = ["test-support"] } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 0723ba689b..3ed311a51d 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -611,12 +611,7 @@ impl LocalLspStore { Some(worktree_path) })?; - let mut child = smol::process::Command::new(command); - #[cfg(target_os = "windows")] - { - use smol::process::windows::CommandExt; - child.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0); - } + let mut child = util::command::new_smol_command(command); if let Some(buffer_env) = buffer.env.as_ref() { child.envs(buffer_env); @@ -7935,7 +7930,7 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate { }; let env = self.shell_env().await; - let output = smol::process::Command::new(&npm) + let output = util::command::new_smol_command(&npm) .args(["root", "-g"]) .envs(env) .current_dir(local_package_directory) @@ -7969,7 +7964,7 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate { async fn try_exec(&self, command: LanguageServerBinary) -> Result<()> { let working_dir = self.worktree_root_path(); - let output = smol::process::Command::new(&command.path) + let output = util::command::new_smol_command(&command.path) .args(command.arguments) .envs(command.env.clone().unwrap_or_default()) .current_dir(working_dir) diff --git a/crates/remote/src/ssh_session.rs b/crates/remote/src/ssh_session.rs index 1ea76a24c8..87a58cb050 100644 --- a/crates/remote/src/ssh_session.rs +++ b/crates/remote/src/ssh_session.rs @@ -255,7 +255,7 @@ impl SshSocket { // and passes -l as an argument to sh, not to ls. // You need to do it like this: $ ssh host "sh -c 'ls -l /tmp'" fn ssh_command(&self, program: &str, args: &[&str]) -> process::Command { - let mut command = process::Command::new("ssh"); + let mut command = util::command::new_smol_command("ssh"); let to_run = iter::once(&program) .chain(args.iter()) .map(|token| { @@ -1224,7 +1224,7 @@ trait RemoteConnection: Send + Sync { struct SshRemoteConnection { socket: SshSocket, - master_process: Mutex>, + master_process: Mutex>, remote_binary_path: Option, _temp_dir: TempDir, } @@ -1258,7 +1258,7 @@ impl RemoteConnection for SshRemoteConnection { dest_path: PathBuf, cx: &AppContext, ) -> Task> { - let mut command = process::Command::new("scp"); + let mut command = util::command::new_smol_command("scp"); let output = self .socket .ssh_options(&mut command) @@ -1910,7 +1910,7 @@ impl SshRemoteConnection { async fn upload_file(&self, src_path: &Path, dest_path: &Path) -> Result<()> { log::debug!("uploading file {:?} to {:?}", src_path, dest_path); - let mut command = process::Command::new("scp"); + let mut command = util::command::new_smol_command("scp"); let output = self .socket .ssh_options(&mut command) diff --git a/crates/repl/Cargo.toml b/crates/repl/Cargo.toml index 3f59ca325b..60e8734771 100644 --- a/crates/repl/Cargo.toml +++ b/crates/repl/Cargo.toml @@ -49,9 +49,6 @@ uuid.workspace = true workspace.workspace = true picker.workspace = true -[target.'cfg(target_os = "windows")'.dependencies] -windows.workspace = true - [dev-dependencies] editor = { workspace = true, features = ["test-support"] } env_logger.workspace = true diff --git a/crates/repl/src/kernels/mod.rs b/crates/repl/src/kernels/mod.rs index cea5adb59e..3fe4c3c12d 100644 --- a/crates/repl/src/kernels/mod.rs +++ b/crates/repl/src/kernels/mod.rs @@ -16,7 +16,6 @@ pub use remote_kernels::*; use anyhow::Result; use runtimelib::{ExecutionState, JupyterKernelspec, JupyterMessage, KernelInfoReply}; -use smol::process::Command; use ui::SharedString; pub type JupyterMessageChannel = stream::SelectAll>; @@ -85,7 +84,7 @@ pub fn python_env_kernel_specifications( let python_path = toolchain.path.to_string(); // Check if ipykernel is installed - let ipykernel_check = Command::new(&python_path) + let ipykernel_check = util::command::new_smol_command(&python_path) .args(&["-c", "import ipykernel"]) .output() .await; diff --git a/crates/repl/src/kernels/native_kernel.rs b/crates/repl/src/kernels/native_kernel.rs index 8a232c3de9..03a57b34ef 100644 --- a/crates/repl/src/kernels/native_kernel.rs +++ b/crates/repl/src/kernels/native_kernel.rs @@ -48,7 +48,7 @@ impl LocalKernelSpecification { self.name ); - let mut cmd = Command::new(&argv[0]); + let mut cmd = util::command::new_smol_command(&argv[0]); for arg in &argv[1..] { if arg == "{connection_file}" { @@ -62,12 +62,6 @@ impl LocalKernelSpecification { cmd.envs(env); } - #[cfg(windows)] - { - use smol::process::windows::CommandExt; - cmd.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0); - } - Ok(cmd) } } @@ -350,17 +344,11 @@ pub async fn local_kernel_specifications(fs: Arc) -> Result, cx: &mut ModelContext, ) -> Result { - let mut process = Command::new(&binary_path); - process + let mut process = util::command::new_smol_command(&binary_path) .arg("stdio") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) - .kill_on_drop(true); - - #[cfg(target_os = "windows")] - { - use smol::process::windows::CommandExt; - process.creation_flags(windows::Win32::System::Threading::CREATE_NO_WINDOW.0); - } - - let mut process = process.spawn().context("failed to start the binary")?; + .kill_on_drop(true) + .spawn() + .context("failed to start the binary")?; let stdin = process .stdin diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 58c4686bf9..94d580e643 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -30,6 +30,7 @@ regex.workspace = true rust-embed.workspace = true serde.workspace = true serde_json.workspace = true +smol.workspace = true take-until = "0.2.0" tempfile = { workspace = true, optional = true } unicase.workspace = true diff --git a/crates/util/src/command.rs b/crates/util/src/command.rs new file mode 100644 index 0000000000..85e2234991 --- /dev/null +++ b/crates/util/src/command.rs @@ -0,0 +1,32 @@ +use std::ffi::OsStr; + +#[cfg(target_os = "windows")] +const CREATE_NO_WINDOW: u32 = 0x0800_0000_u32; + +#[cfg(target_os = "windows")] +pub fn new_std_command(program: impl AsRef) -> std::process::Command { + use std::os::windows::process::CommandExt; + + let mut command = std::process::Command::new(program); + command.creation_flags(CREATE_NO_WINDOW); + command +} + +#[cfg(not(target_os = "windows"))] +pub fn new_std_command(program: impl AsRef) -> std::process::Command { + std::process::Command::new(program) +} + +#[cfg(target_os = "windows")] +pub fn new_smol_command(program: impl AsRef) -> smol::process::Command { + use smol::process::windows::CommandExt; + + let mut command = smol::process::Command::new(program); + command.creation_flags(CREATE_NO_WINDOW); + command +} + +#[cfg(not(target_os = "windows"))] +pub fn new_smol_command(program: impl AsRef) -> smol::process::Command { + smol::process::Command::new(program) +} diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index e27fd65ac7..5141f85797 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -1,4 +1,5 @@ pub mod arc_cow; +pub mod command; pub mod fs; pub mod paths; pub mod serde; From 0e62b6dddd0ff9b6168b1c9ce1519efa109d5973 Mon Sep 17 00:00:00 2001 From: Ryan Hawkins Date: Wed, 20 Nov 2024 18:00:21 -0700 Subject: [PATCH 086/157] Add `file_scan_inclusions` setting to customize Zed file indexing (#16852) Closes #4745 Release Notes: - Added a new `file_scan_inclusions` setting to force Zed to index files that match the provided globs, even if they're gitignored. --------- Co-authored-by: Mikayla Maki --- assets/settings/default.json | 10 +- crates/project_panel/src/project_panel.rs | 1 + crates/worktree/Cargo.toml | 2 +- crates/worktree/src/worktree.rs | 85 ++++++-- crates/worktree/src/worktree_settings.rs | 36 +++- crates/worktree/src/worktree_tests.rs | 241 +++++++++++++++++++++- 6 files changed, 350 insertions(+), 25 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 3757dfe119..d654082e24 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -668,7 +668,7 @@ }, // Add files or globs of files that will be excluded by Zed entirely: // they will be skipped during FS scan(s), file tree and file search - // will lack the corresponding file entries. + // will lack the corresponding file entries. Overrides `file_scan_inclusions`. "file_scan_exclusions": [ "**/.git", "**/.svn", @@ -679,6 +679,14 @@ "**/.classpath", "**/.settings" ], + // Add files or globs of files that will be included by Zed, even when + // ignored by git. This is useful for files that are not tracked by git, + // but are still important to your project. Note that globs that are + // overly broad can slow down Zed's file scanning. Overridden by `file_scan_exclusions`. + "file_scan_inclusions": [ + ".env*", + "docker-compose.*.yml" + ], // Git gutter behavior configuration. "git": { // Control whether the git gutter is shown. May take 2 values: diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 94472f5576..9432d1e6d5 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -2033,6 +2033,7 @@ impl ProjectPanel { is_ignored: entry.is_ignored, is_external: false, is_private: false, + is_always_included: entry.is_always_included, git_status: entry.git_status, canonical_path: entry.canonical_path.clone(), char_bag: entry.char_bag, diff --git a/crates/worktree/Cargo.toml b/crates/worktree/Cargo.toml index da3676f15c..adbbf66d23 100644 --- a/crates/worktree/Cargo.toml +++ b/crates/worktree/Cargo.toml @@ -37,7 +37,7 @@ log.workspace = true parking_lot.workspace = true paths.workspace = true postage.workspace = true -rpc.workspace = true +rpc = { workspace = true, features = ["gpui"] } schemars.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 5bd064b534..bf072ca549 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -65,7 +65,10 @@ use std::{ }; use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; use text::{LineEnding, Rope}; -use util::{paths::home_dir, ResultExt}; +use util::{ + paths::{home_dir, PathMatcher}, + ResultExt, +}; pub use worktree_settings::WorktreeSettings; #[cfg(feature = "test-support")] @@ -134,6 +137,7 @@ pub struct RemoteWorktree { background_snapshot: Arc)>>, project_id: u64, client: AnyProtoClient, + file_scan_inclusions: PathMatcher, updates_tx: Option>, update_observer: Option>, snapshot_subscriptions: VecDeque<(usize, oneshot::Sender<()>)>, @@ -150,6 +154,7 @@ pub struct Snapshot { root_char_bag: CharBag, entries_by_path: SumTree, entries_by_id: SumTree, + always_included_entries: Vec>, repository_entries: TreeMap, /// A number that increases every time the worktree begins scanning @@ -433,7 +438,7 @@ impl Worktree { cx.observe_global::(move |this, cx| { if let Self::Local(this) = this { let settings = WorktreeSettings::get(settings_location, cx).clone(); - if settings != this.settings { + if this.settings != settings { this.settings = settings; this.restart_background_scanners(cx); } @@ -480,11 +485,19 @@ impl Worktree { let (background_updates_tx, mut background_updates_rx) = mpsc::unbounded(); let (mut snapshot_updated_tx, mut snapshot_updated_rx) = watch::channel(); + let worktree_id = snapshot.id(); + let settings_location = Some(SettingsLocation { + worktree_id, + path: Path::new(EMPTY_PATH), + }); + + let settings = WorktreeSettings::get(settings_location, cx).clone(); let worktree = RemoteWorktree { client, project_id, replica_id, snapshot, + file_scan_inclusions: settings.file_scan_inclusions.clone(), background_snapshot: background_snapshot.clone(), updates_tx: Some(background_updates_tx), update_observer: None, @@ -500,7 +513,10 @@ impl Worktree { while let Some(update) = background_updates_rx.next().await { { let mut lock = background_snapshot.lock(); - if let Err(error) = lock.0.apply_remote_update(update.clone()) { + if let Err(error) = lock + .0 + .apply_remote_update(update.clone(), &settings.file_scan_inclusions) + { log::error!("error applying worktree update: {}", error); } lock.1.push(update); @@ -1022,7 +1038,17 @@ impl LocalWorktree { let (path_prefixes_to_scan_tx, path_prefixes_to_scan_rx) = channel::unbounded(); self.scan_requests_tx = scan_requests_tx; self.path_prefixes_to_scan_tx = path_prefixes_to_scan_tx; + self.start_background_scanner(scan_requests_rx, path_prefixes_to_scan_rx, cx); + let always_included_entries = mem::take(&mut self.snapshot.always_included_entries); + log::debug!( + "refreshing entries for the following always included paths: {:?}", + always_included_entries + ); + + // Cleans up old always included entries to ensure they get updated properly. Otherwise, + // nested always included entries may not get updated and will result in out-of-date info. + self.refresh_entries_for_paths(always_included_entries); } fn start_background_scanner( @@ -1971,7 +1997,7 @@ impl RemoteWorktree { this.update(&mut cx, |worktree, _| { let worktree = worktree.as_remote_mut().unwrap(); let snapshot = &mut worktree.background_snapshot.lock().0; - let entry = snapshot.insert_entry(entry); + let entry = snapshot.insert_entry(entry, &worktree.file_scan_inclusions); worktree.snapshot = snapshot.clone(); entry })? @@ -2052,6 +2078,7 @@ impl Snapshot { abs_path, root_char_bag: root_name.chars().map(|c| c.to_ascii_lowercase()).collect(), root_name, + always_included_entries: Default::default(), entries_by_path: Default::default(), entries_by_id: Default::default(), repository_entries: Default::default(), @@ -2115,8 +2142,12 @@ impl Snapshot { self.entries_by_id.get(&entry_id, &()).is_some() } - fn insert_entry(&mut self, entry: proto::Entry) -> Result { - let entry = Entry::try_from((&self.root_char_bag, entry))?; + fn insert_entry( + &mut self, + entry: proto::Entry, + always_included_paths: &PathMatcher, + ) -> Result { + let entry = Entry::try_from((&self.root_char_bag, always_included_paths, entry))?; let old_entry = self.entries_by_id.insert_or_replace( PathEntry { id: entry.id, @@ -2170,7 +2201,11 @@ impl Snapshot { } } - pub(crate) fn apply_remote_update(&mut self, mut update: proto::UpdateWorktree) -> Result<()> { + pub(crate) fn apply_remote_update( + &mut self, + mut update: proto::UpdateWorktree, + always_included_paths: &PathMatcher, + ) -> Result<()> { log::trace!( "applying remote worktree update. {} entries updated, {} removed", update.updated_entries.len(), @@ -2193,7 +2228,7 @@ impl Snapshot { } for entry in update.updated_entries { - let entry = Entry::try_from((&self.root_char_bag, entry))?; + let entry = Entry::try_from((&self.root_char_bag, always_included_paths, entry))?; if let Some(PathEntry { path, .. }) = self.entries_by_id.get(&entry.id, &()) { entries_by_path_edits.push(Edit::Remove(PathKey(path.clone()))); } @@ -2713,7 +2748,7 @@ impl LocalSnapshot { for entry in self.entries_by_path.cursor::<()>(&()) { if entry.is_file() { assert_eq!(files.next().unwrap().inode, entry.inode); - if !entry.is_ignored && !entry.is_external { + if (!entry.is_ignored && !entry.is_external) || entry.is_always_included { assert_eq!(visible_files.next().unwrap().inode, entry.inode); } } @@ -2796,7 +2831,7 @@ impl LocalSnapshot { impl BackgroundScannerState { fn should_scan_directory(&self, entry: &Entry) -> bool { - (!entry.is_external && !entry.is_ignored) + (!entry.is_external && (!entry.is_ignored || entry.is_always_included)) || entry.path.file_name() == Some(*DOT_GIT) || entry.path.file_name() == Some(local_settings_folder_relative_path().as_os_str()) || self.scanned_dirs.contains(&entry.id) // If we've ever scanned it, keep scanning @@ -3369,6 +3404,12 @@ pub struct Entry { /// exclude them from searches. pub is_ignored: bool, + /// Whether this entry is always included in searches. + /// + /// This is used for entries that are always included in searches, even + /// if they are ignored by git. Overridden by file_scan_exclusions. + pub is_always_included: bool, + /// Whether this entry's canonical path is outside of the worktree. /// This means the entry is only accessible from the worktree root via a /// symlink. @@ -3440,6 +3481,7 @@ impl Entry { size: metadata.len, canonical_path, is_ignored: false, + is_always_included: false, is_external: false, is_private: false, git_status: None, @@ -3486,7 +3528,8 @@ impl sum_tree::Item for Entry { type Summary = EntrySummary; fn summary(&self, _cx: &()) -> Self::Summary { - let non_ignored_count = if self.is_ignored || self.is_external { + let non_ignored_count = if (self.is_ignored || self.is_external) && !self.is_always_included + { 0 } else { 1 @@ -4254,6 +4297,7 @@ impl BackgroundScanner { if child_entry.is_dir() { child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, true); + child_entry.is_always_included = self.settings.is_path_always_included(&child_path); // Avoid recursing until crash in the case of a recursive symlink if job.ancestor_inodes.contains(&child_entry.inode) { @@ -4278,6 +4322,7 @@ impl BackgroundScanner { } } else { child_entry.is_ignored = ignore_stack.is_abs_path_ignored(&child_abs_path, false); + child_entry.is_always_included = self.settings.is_path_always_included(&child_path); if !child_entry.is_ignored { if let Some(repo) = &containing_repository { if let Ok(repo_path) = child_entry.path.strip_prefix(&repo.work_directory) { @@ -4314,6 +4359,12 @@ impl BackgroundScanner { new_jobs.remove(job_ix); } } + if entry.is_always_included { + state + .snapshot + .always_included_entries + .push(entry.path.clone()); + } } state.populate_dir(&job.path, new_entries, new_ignore); @@ -4430,6 +4481,7 @@ impl BackgroundScanner { fs_entry.is_ignored = ignore_stack.is_abs_path_ignored(&abs_path, is_dir); fs_entry.is_external = is_external; fs_entry.is_private = self.is_path_private(path); + fs_entry.is_always_included = self.settings.is_path_always_included(path); if let (Some(scan_queue_tx), true) = (&scan_queue_tx, is_dir) { if state.should_scan_directory(&fs_entry) @@ -5317,7 +5369,7 @@ impl<'a> Traversal<'a> { if let Some(entry) = self.cursor.item() { if (self.include_files || !entry.is_file()) && (self.include_dirs || !entry.is_dir()) - && (self.include_ignored || !entry.is_ignored) + && (self.include_ignored || !entry.is_ignored || entry.is_always_included) { return true; } @@ -5448,10 +5500,12 @@ impl<'a> From<&'a Entry> for proto::Entry { } } -impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry { +impl<'a> TryFrom<(&'a CharBag, &PathMatcher, proto::Entry)> for Entry { type Error = anyhow::Error; - fn try_from((root_char_bag, entry): (&'a CharBag, proto::Entry)) -> Result { + fn try_from( + (root_char_bag, always_included, entry): (&'a CharBag, &PathMatcher, proto::Entry), + ) -> Result { let kind = if entry.is_dir { EntryKind::Dir } else { @@ -5462,7 +5516,7 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry { Ok(Entry { id: ProjectEntryId::from_proto(entry.id), kind, - path, + path: path.clone(), inode: entry.inode, mtime: entry.mtime.map(|time| time.into()), size: entry.size.unwrap_or(0), @@ -5470,6 +5524,7 @@ impl<'a> TryFrom<(&'a CharBag, proto::Entry)> for Entry { .canonical_path .map(|path_string| Box::from(Path::new(&path_string))), is_ignored: entry.is_ignored, + is_always_included: always_included.is_match(path.as_ref()), is_external: entry.is_external, git_status: git_status_from_proto(entry.git_status), is_private: false, diff --git a/crates/worktree/src/worktree_settings.rs b/crates/worktree/src/worktree_settings.rs index 32851d963a..f26dc4af0f 100644 --- a/crates/worktree/src/worktree_settings.rs +++ b/crates/worktree/src/worktree_settings.rs @@ -9,6 +9,7 @@ use util::paths::PathMatcher; #[derive(Clone, PartialEq, Eq)] pub struct WorktreeSettings { + pub file_scan_inclusions: PathMatcher, pub file_scan_exclusions: PathMatcher, pub private_files: PathMatcher, } @@ -21,13 +22,19 @@ impl WorktreeSettings { pub fn is_path_excluded(&self, path: &Path) -> bool { path.ancestors() - .any(|ancestor| self.file_scan_exclusions.is_match(ancestor)) + .any(|ancestor| self.file_scan_exclusions.is_match(&ancestor)) + } + + pub fn is_path_always_included(&self, path: &Path) -> bool { + path.ancestors() + .any(|ancestor| self.file_scan_inclusions.is_match(&ancestor)) } } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct WorktreeSettingsContent { - /// Completely ignore files matching globs from `file_scan_exclusions` + /// Completely ignore files matching globs from `file_scan_exclusions`. Overrides + /// `file_scan_inclusions`. /// /// Default: [ /// "**/.git", @@ -42,6 +49,15 @@ pub struct WorktreeSettingsContent { #[serde(default)] pub file_scan_exclusions: Option>, + /// Always include files that match these globs when scanning for files, even if they're + /// ignored by git. This setting is overridden by `file_scan_exclusions`. + /// Default: [ + /// ".env*", + /// "docker-compose.*.yml", + /// ] + #[serde(default)] + pub file_scan_inclusions: Option>, + /// Treat the files matching these globs as `.env` files. /// Default: [ "**/.env*" ] pub private_files: Option>, @@ -59,11 +75,27 @@ impl Settings for WorktreeSettings { let result: WorktreeSettingsContent = sources.json_merge()?; let mut file_scan_exclusions = result.file_scan_exclusions.unwrap_or_default(); let mut private_files = result.private_files.unwrap_or_default(); + let mut parsed_file_scan_inclusions: Vec = result + .file_scan_inclusions + .unwrap_or_default() + .iter() + .flat_map(|glob| { + Path::new(glob) + .ancestors() + .map(|a| a.to_string_lossy().into()) + }) + .filter(|p| p != "") + .collect(); file_scan_exclusions.sort(); private_files.sort(); + parsed_file_scan_inclusions.sort(); Ok(Self { file_scan_exclusions: path_matchers(&file_scan_exclusions, "file_scan_exclusions")?, private_files: path_matchers(&private_files, "private_files")?, + file_scan_inclusions: path_matchers( + &parsed_file_scan_inclusions, + "file_scan_inclusions", + )?, }) } } diff --git a/crates/worktree/src/worktree_tests.rs b/crates/worktree/src/worktree_tests.rs index 75f86fa606..fbedd896e3 100644 --- a/crates/worktree/src/worktree_tests.rs +++ b/crates/worktree/src/worktree_tests.rs @@ -878,6 +878,211 @@ async fn test_write_file(cx: &mut TestAppContext) { }); } +#[gpui::test] +async fn test_file_scan_inclusions(cx: &mut TestAppContext) { + init_test(cx); + cx.executor().allow_parking(); + let dir = temp_tree(json!({ + ".gitignore": "**/target\n/node_modules\ntop_level.txt\n", + "target": { + "index": "blah2" + }, + "node_modules": { + ".DS_Store": "", + "prettier": { + "package.json": "{}", + }, + }, + "src": { + ".DS_Store": "", + "foo": { + "foo.rs": "mod another;\n", + "another.rs": "// another", + }, + "bar": { + "bar.rs": "// bar", + }, + "lib.rs": "mod foo;\nmod bar;\n", + }, + "top_level.txt": "top level file", + ".DS_Store": "", + })); + cx.update(|cx| { + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |project_settings| { + project_settings.file_scan_exclusions = Some(vec![]); + project_settings.file_scan_inclusions = Some(vec![ + "node_modules/**/package.json".to_string(), + "**/.DS_Store".to_string(), + ]); + }); + }); + }); + + let tree = Worktree::local( + dir.path(), + true, + Arc::new(RealFs::default()), + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + tree.flush_fs_events(cx).await; + tree.read_with(cx, |tree, _| { + // Assert that file_scan_inclusions overrides file_scan_exclusions. + check_worktree_entries( + tree, + &[], + &["target", "node_modules"], + &["src/lib.rs", "src/bar/bar.rs", ".gitignore"], + &[ + "node_modules/prettier/package.json", + ".DS_Store", + "node_modules/.DS_Store", + "src/.DS_Store", + ], + ) + }); +} + +#[gpui::test] +async fn test_file_scan_exclusions_overrules_inclusions(cx: &mut TestAppContext) { + init_test(cx); + cx.executor().allow_parking(); + let dir = temp_tree(json!({ + ".gitignore": "**/target\n/node_modules\n", + "target": { + "index": "blah2" + }, + "node_modules": { + ".DS_Store": "", + "prettier": { + "package.json": "{}", + }, + }, + "src": { + ".DS_Store": "", + "foo": { + "foo.rs": "mod another;\n", + "another.rs": "// another", + }, + }, + ".DS_Store": "", + })); + + cx.update(|cx| { + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |project_settings| { + project_settings.file_scan_exclusions = Some(vec!["**/.DS_Store".to_string()]); + project_settings.file_scan_inclusions = Some(vec!["**/.DS_Store".to_string()]); + }); + }); + }); + + let tree = Worktree::local( + dir.path(), + true, + Arc::new(RealFs::default()), + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + tree.flush_fs_events(cx).await; + tree.read_with(cx, |tree, _| { + // Assert that file_scan_inclusions overrides file_scan_exclusions. + check_worktree_entries( + tree, + &[".DS_Store, src/.DS_Store"], + &["target", "node_modules"], + &["src/foo/another.rs", "src/foo/foo.rs", ".gitignore"], + &[], + ) + }); +} + +#[gpui::test] +async fn test_file_scan_inclusions_reindexes_on_setting_change(cx: &mut TestAppContext) { + init_test(cx); + cx.executor().allow_parking(); + let dir = temp_tree(json!({ + ".gitignore": "**/target\n/node_modules/\n", + "target": { + "index": "blah2" + }, + "node_modules": { + ".DS_Store": "", + "prettier": { + "package.json": "{}", + }, + }, + "src": { + ".DS_Store": "", + "foo": { + "foo.rs": "mod another;\n", + "another.rs": "// another", + }, + }, + ".DS_Store": "", + })); + + cx.update(|cx| { + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |project_settings| { + project_settings.file_scan_exclusions = Some(vec![]); + project_settings.file_scan_inclusions = Some(vec!["node_modules/**".to_string()]); + }); + }); + }); + let tree = Worktree::local( + dir.path(), + true, + Arc::new(RealFs::default()), + Default::default(), + &mut cx.to_async(), + ) + .await + .unwrap(); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _| { + assert!(tree + .entry_for_path("node_modules") + .is_some_and(|f| f.is_always_included)); + assert!(tree + .entry_for_path("node_modules/prettier/package.json") + .is_some_and(|f| f.is_always_included)); + }); + + cx.update(|cx| { + cx.update_global::(|store, cx| { + store.update_user_settings::(cx, |project_settings| { + project_settings.file_scan_exclusions = Some(vec![]); + project_settings.file_scan_inclusions = Some(vec![]); + }); + }); + }); + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + tree.flush_fs_events(cx).await; + + tree.read_with(cx, |tree, _| { + assert!(tree + .entry_for_path("node_modules") + .is_some_and(|f| !f.is_always_included)); + assert!(tree + .entry_for_path("node_modules/prettier/package.json") + .is_some_and(|f| !f.is_always_included)); + }); +} + #[gpui::test] async fn test_file_scan_exclusions(cx: &mut TestAppContext) { init_test(cx); @@ -939,6 +1144,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { ], &["target", "node_modules"], &["src/lib.rs", "src/bar/bar.rs", ".gitignore"], + &[], ) }); @@ -970,6 +1176,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) { "src/.DS_Store", ".DS_Store", ], + &[], ) }); } @@ -1051,6 +1258,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) { "src/bar/bar.rs", ".gitignore", ], + &[], ) }); @@ -1111,6 +1319,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) { "src/new_file", ".gitignore", ], + &[], ) }); } @@ -1140,14 +1349,14 @@ async fn test_fs_events_in_dot_git_worktree(cx: &mut TestAppContext) { .await; tree.flush_fs_events(cx).await; tree.read_with(cx, |tree, _| { - check_worktree_entries(tree, &[], &["HEAD", "foo"], &[]) + check_worktree_entries(tree, &[], &["HEAD", "foo"], &[], &[]) }); std::fs::write(dot_git_worktree_dir.join("new_file"), "new file contents") .unwrap_or_else(|e| panic!("Failed to create in {dot_git_worktree_dir:?} a new file: {e}")); tree.flush_fs_events(cx).await; tree.read_with(cx, |tree, _| { - check_worktree_entries(tree, &[], &["HEAD", "foo", "new_file"], &[]) + check_worktree_entries(tree, &[], &["HEAD", "foo", "new_file"], &[], &[]) }); } @@ -1180,8 +1389,12 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) { let snapshot = Arc::new(Mutex::new(tree.snapshot())); tree.observe_updates(0, cx, { let snapshot = snapshot.clone(); + let settings = tree.settings().clone(); move |update| { - snapshot.lock().apply_remote_update(update).unwrap(); + snapshot + .lock() + .apply_remote_update(update, &settings.file_scan_inclusions) + .unwrap(); async { true } } }); @@ -1474,12 +1687,14 @@ async fn test_random_worktree_operations_during_initial_scan( snapshot }); + let settings = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().settings()); + for (i, snapshot) in snapshots.into_iter().enumerate().rev() { let mut updated_snapshot = snapshot.clone(); for update in updates.lock().iter() { if update.scan_id >= updated_snapshot.scan_id() as u64 { updated_snapshot - .apply_remote_update(update.clone()) + .apply_remote_update(update.clone(), &settings.file_scan_inclusions) .unwrap(); } } @@ -1610,10 +1825,14 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng) ); } + let settings = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().settings()); + for (i, mut prev_snapshot) in snapshots.into_iter().enumerate().rev() { for update in updates.lock().iter() { if update.scan_id >= prev_snapshot.scan_id() as u64 { - prev_snapshot.apply_remote_update(update.clone()).unwrap(); + prev_snapshot + .apply_remote_update(update.clone(), &settings.file_scan_inclusions) + .unwrap(); } } @@ -2588,6 +2807,7 @@ fn check_worktree_entries( expected_excluded_paths: &[&str], expected_ignored_paths: &[&str], expected_tracked_paths: &[&str], + expected_included_paths: &[&str], ) { for path in expected_excluded_paths { let entry = tree.entry_for_path(path); @@ -2610,10 +2830,19 @@ fn check_worktree_entries( .entry_for_path(path) .unwrap_or_else(|| panic!("Missing entry for expected tracked path '{path}'")); assert!( - !entry.is_ignored, + !entry.is_ignored || entry.is_always_included, "expected path '{path}' to be tracked, but got entry: {entry:?}", ); } + for path in expected_included_paths { + let entry = tree + .entry_for_path(path) + .unwrap_or_else(|| panic!("Missing entry for expected included path '{path}'")); + assert!( + entry.is_always_included, + "expected path '{path}' to always be included, but got entry: {entry:?}", + ); + } } fn init_test(cx: &mut gpui::TestAppContext) { From a03770837ea2cee44811181aa3bc413767668a25 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 20 Nov 2024 18:21:09 -0800 Subject: [PATCH 087/157] Add extensions to the remote server (#20049) TODO: - [x] Double check strange PHP env detection - [x] Clippy & etc. Release Notes: - Added support for extension languages on the remote server --------- Co-authored-by: Conrad Irwin --- Cargo.lock | 4 + crates/extension_host/Cargo.toml | 2 + crates/extension_host/src/extension_host.rs | 158 +++++++- crates/extension_host/src/headless_host.rs | 379 ++++++++++++++++++ crates/proto/proto/zed.proto | 28 +- crates/proto/src/proto.rs | 5 + crates/recent_projects/Cargo.toml | 1 + crates/recent_projects/src/ssh_connections.rs | 10 + crates/remote/src/ssh_session.rs | 1 + crates/remote_server/Cargo.toml | 1 + crates/remote_server/src/headless_project.rs | 21 + 11 files changed, 606 insertions(+), 4 deletions(-) create mode 100644 crates/extension_host/src/headless_host.rs diff --git a/Cargo.lock b/Cargo.lock index 8db4b8424d..49c4a10efc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4170,6 +4170,7 @@ dependencies = [ "paths", "project", "release_channel", + "remote", "reqwest_client", "schemars", "semantic_version", @@ -4178,6 +4179,7 @@ dependencies = [ "serde_json_lenient", "settings", "task", + "tempfile", "theme", "toml 0.8.19", "url", @@ -9677,6 +9679,7 @@ dependencies = [ "anyhow", "auto_update", "editor", + "extension_host", "file_finder", "futures 0.3.31", "fuzzy", @@ -9852,6 +9855,7 @@ dependencies = [ "client", "clock", "env_logger 0.11.5", + "extension_host", "fork", "fs", "futures 0.3.31", diff --git a/crates/extension_host/Cargo.toml b/crates/extension_host/Cargo.toml index 856466e1a1..31d3df88aa 100644 --- a/crates/extension_host/Cargo.toml +++ b/crates/extension_host/Cargo.toml @@ -34,6 +34,7 @@ lsp.workspace = true node_runtime.workspace = true paths.workspace = true project.workspace = true +remote.workspace = true release_channel.workspace = true schemars.workspace = true semantic_version.workspace = true @@ -42,6 +43,7 @@ serde_json.workspace = true serde_json_lenient.workspace = true settings.workspace = true task.workspace = true +tempfile.workspace = true toml.workspace = true url.workspace = true util.workspace = true diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 1adea4e0fb..a858123fd9 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -1,5 +1,6 @@ pub mod extension_lsp_adapter; pub mod extension_settings; +pub mod headless_host; pub mod wasm_host; #[cfg(test)] @@ -9,8 +10,8 @@ use crate::extension_lsp_adapter::ExtensionLspAdapter; use anyhow::{anyhow, bail, Context as _, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; -use client::{telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse}; -use collections::{btree_map, BTreeMap, HashSet}; +use client::{proto, telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse}; +use collections::{btree_map, BTreeMap, HashMap, HashSet}; use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder}; use extension::Extension; pub use extension::ExtensionManifest; @@ -36,6 +37,7 @@ use lsp::LanguageServerName; use node_runtime::NodeRuntime; use project::ContextProviderWithTasks; use release_channel::ReleaseChannel; +use remote::SshRemoteClient; use semantic_version::SemanticVersion; use serde::{Deserialize, Serialize}; use settings::Settings; @@ -178,6 +180,8 @@ pub struct ExtensionStore { pub wasm_host: Arc, pub wasm_extensions: Vec<(Arc, WasmExtension)>, pub tasks: Vec>, + pub ssh_clients: HashMap>, + pub ssh_registered_tx: UnboundedSender<()>, } #[derive(Clone, Copy)] @@ -289,6 +293,7 @@ impl ExtensionStore { let index_path = extensions_dir.join("index.json"); let (reload_tx, mut reload_rx) = unbounded(); + let (connection_registered_tx, mut connection_registered_rx) = unbounded(); let mut this = Self { registration_hooks: extension_api.clone(), extension_index: Default::default(), @@ -312,6 +317,9 @@ impl ExtensionStore { telemetry, reload_tx, tasks: Vec::new(), + + ssh_clients: HashMap::default(), + ssh_registered_tx: connection_registered_tx, }; // The extensions store maintains an index file, which contains a complete @@ -386,6 +394,14 @@ impl ExtensionStore { .await; index_changed = false; } + + Self::update_ssh_clients(&this, &mut cx).await?; + } + _ = connection_registered_rx.next() => { + debounce_timer = cx + .background_executor() + .timer(RELOAD_DEBOUNCE_DURATION) + .fuse(); } extension_id = reload_rx.next() => { let Some(extension_id) = extension_id else { break; }; @@ -1431,6 +1447,144 @@ impl ExtensionStore { Ok(()) } + + fn prepare_remote_extension( + &mut self, + extension_id: Arc, + tmp_dir: PathBuf, + cx: &mut ModelContext, + ) -> Task> { + let src_dir = self.extensions_dir().join(extension_id.as_ref()); + let Some(loaded_extension) = self.extension_index.extensions.get(&extension_id).cloned() + else { + return Task::ready(Err(anyhow!("extension no longer installed"))); + }; + let fs = self.fs.clone(); + cx.background_executor().spawn(async move { + for well_known_path in ["extension.toml", "extension.json", "extension.wasm"] { + if fs.is_file(&src_dir.join(well_known_path)).await { + fs.copy_file( + &src_dir.join(well_known_path), + &tmp_dir.join(well_known_path), + fs::CopyOptions::default(), + ) + .await? + } + } + + for language_path in loaded_extension.manifest.languages.iter() { + if fs + .is_file(&src_dir.join(language_path).join("config.toml")) + .await + { + fs.create_dir(&tmp_dir.join(language_path)).await?; + fs.copy_file( + &src_dir.join(language_path).join("config.toml"), + &tmp_dir.join(language_path).join("config.toml"), + fs::CopyOptions::default(), + ) + .await? + } + } + + Ok(()) + }) + } + + async fn sync_extensions_over_ssh( + this: &WeakModel, + client: WeakModel, + cx: &mut AsyncAppContext, + ) -> Result<()> { + let extensions = this.update(cx, |this, _cx| { + this.extension_index + .extensions + .iter() + .filter_map(|(id, entry)| { + if entry.manifest.language_servers.is_empty() { + return None; + } + Some(proto::Extension { + id: id.to_string(), + version: entry.manifest.version.to_string(), + dev: entry.dev, + }) + }) + .collect() + })?; + + let response = client + .update(cx, |client, _cx| { + client + .proto_client() + .request(proto::SyncExtensions { extensions }) + })? + .await?; + + for missing_extension in response.missing_extensions.into_iter() { + let tmp_dir = tempfile::tempdir()?; + this.update(cx, |this, cx| { + this.prepare_remote_extension( + missing_extension.id.clone().into(), + tmp_dir.path().to_owned(), + cx, + ) + })? + .await?; + let dest_dir = PathBuf::from(&response.tmp_dir).join(missing_extension.clone().id); + log::info!("Uploading extension {}", missing_extension.clone().id); + + client + .update(cx, |client, cx| { + client.upload_directory(tmp_dir.path().to_owned(), dest_dir.clone(), cx) + })? + .await?; + + client + .update(cx, |client, _cx| { + client.proto_client().request(proto::InstallExtension { + tmp_dir: dest_dir.to_string_lossy().to_string(), + extension: Some(missing_extension), + }) + })? + .await?; + } + + anyhow::Ok(()) + } + + pub async fn update_ssh_clients( + this: &WeakModel, + cx: &mut AsyncAppContext, + ) -> Result<()> { + let clients = this.update(cx, |this, _cx| { + this.ssh_clients.retain(|_k, v| v.upgrade().is_some()); + this.ssh_clients.values().cloned().collect::>() + })?; + + for client in clients { + Self::sync_extensions_over_ssh(&this, client, cx) + .await + .log_err(); + } + + anyhow::Ok(()) + } + + pub fn register_ssh_client( + &mut self, + client: Model, + cx: &mut ModelContext, + ) { + let connection_options = client.read(cx).connection_options(); + if self.ssh_clients.contains_key(&connection_options.ssh_url()) { + return; + } + + self.ssh_clients + .insert(connection_options.ssh_url(), client.downgrade()); + self.ssh_registered_tx.unbounded_send(()).ok(); + } } fn load_plugin_queries(root_path: &Path) -> LanguageQueries { diff --git a/crates/extension_host/src/headless_host.rs b/crates/extension_host/src/headless_host.rs new file mode 100644 index 0000000000..e297794bf1 --- /dev/null +++ b/crates/extension_host/src/headless_host.rs @@ -0,0 +1,379 @@ +use std::{path::PathBuf, sync::Arc}; + +use anyhow::{anyhow, Context as _, Result}; +use client::{proto, TypedEnvelope}; +use collections::{HashMap, HashSet}; +use extension::{Extension, ExtensionManifest}; +use fs::{Fs, RemoveOptions, RenameOptions}; +use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, Task, WeakModel}; +use http_client::HttpClient; +use language::{LanguageConfig, LanguageName, LanguageQueries, LanguageRegistry, LoadedLanguage}; +use lsp::LanguageServerName; +use node_runtime::NodeRuntime; + +use crate::{ + extension_lsp_adapter::ExtensionLspAdapter, + wasm_host::{WasmExtension, WasmHost}, + ExtensionRegistrationHooks, +}; + +pub struct HeadlessExtensionStore { + pub registration_hooks: Arc, + pub fs: Arc, + pub extension_dir: PathBuf, + pub wasm_host: Arc, + pub loaded_extensions: HashMap, Arc>, + pub loaded_languages: HashMap, Vec>, + pub loaded_language_servers: HashMap, Vec<(LanguageServerName, LanguageName)>>, +} + +#[derive(Clone, Debug)] +pub struct ExtensionVersion { + pub id: String, + pub version: String, + pub dev: bool, +} + +impl HeadlessExtensionStore { + pub fn new( + fs: Arc, + http_client: Arc, + languages: Arc, + extension_dir: PathBuf, + node_runtime: NodeRuntime, + cx: &mut AppContext, + ) -> Model { + let registration_hooks = Arc::new(HeadlessRegistrationHooks::new(languages.clone())); + cx.new_model(|cx| Self { + registration_hooks: registration_hooks.clone(), + fs: fs.clone(), + wasm_host: WasmHost::new( + fs.clone(), + http_client.clone(), + node_runtime, + registration_hooks, + extension_dir.join("work"), + cx, + ), + extension_dir, + loaded_extensions: Default::default(), + loaded_languages: Default::default(), + loaded_language_servers: Default::default(), + }) + } + + pub fn sync_extensions( + &mut self, + extensions: Vec, + cx: &ModelContext, + ) -> Task>> { + let on_client = HashSet::from_iter(extensions.iter().map(|e| e.id.as_str())); + let to_remove: Vec> = self + .loaded_extensions + .keys() + .filter(|id| !on_client.contains(id.as_ref())) + .cloned() + .collect(); + let to_load: Vec = extensions + .into_iter() + .filter(|e| { + if e.dev { + return true; + } + !self + .loaded_extensions + .get(e.id.as_str()) + .is_some_and(|loaded| loaded.as_ref() == e.version.as_str()) + }) + .collect(); + + cx.spawn(|this, mut cx| async move { + let mut missing = Vec::new(); + + for extension_id in to_remove { + log::info!("removing extension: {}", extension_id); + this.update(&mut cx, |this, cx| { + this.uninstall_extension(&extension_id, cx) + })? + .await?; + } + + for extension in to_load { + if let Err(e) = Self::load_extension(this.clone(), extension.clone(), &mut cx).await + { + log::info!("failed to load extension: {}, {:?}", extension.id, e); + missing.push(extension) + } else if extension.dev { + missing.push(extension) + } + } + + Ok(missing) + }) + } + + pub async fn load_extension( + this: WeakModel, + extension: ExtensionVersion, + cx: &mut AsyncAppContext, + ) -> Result<()> { + let (fs, wasm_host, extension_dir) = this.update(cx, |this, _cx| { + this.loaded_extensions.insert( + extension.id.clone().into(), + extension.version.clone().into(), + ); + ( + this.fs.clone(), + this.wasm_host.clone(), + this.extension_dir.join(&extension.id), + ) + })?; + + let manifest = Arc::new(ExtensionManifest::load(fs.clone(), &extension_dir).await?); + + debug_assert!(!manifest.languages.is_empty() || !manifest.language_servers.is_empty()); + + if manifest.version.as_ref() != extension.version.as_str() { + anyhow::bail!( + "mismatched versions: ({}) != ({})", + manifest.version, + extension.version + ) + } + + for language_path in &manifest.languages { + let language_path = extension_dir.join(language_path); + let config = fs.load(&language_path.join("config.toml")).await?; + let mut config = ::toml::from_str::(&config)?; + + this.update(cx, |this, _cx| { + this.loaded_languages + .entry(manifest.id.clone()) + .or_default() + .push(config.name.clone()); + + config.grammar = None; + + this.registration_hooks.register_language( + config.name.clone(), + None, + config.matcher.clone(), + Arc::new(move || { + Ok(LoadedLanguage { + config: config.clone(), + queries: LanguageQueries::default(), + context_provider: None, + toolchain_provider: None, + }) + }), + ); + })?; + } + + if manifest.language_servers.is_empty() { + return Ok(()); + } + + let wasm_extension: Arc = + Arc::new(WasmExtension::load(extension_dir, &manifest, wasm_host.clone(), &cx).await?); + + for (language_server_name, language_server_config) in &manifest.language_servers { + for language in language_server_config.languages() { + this.update(cx, |this, _cx| { + this.loaded_language_servers + .entry(manifest.id.clone()) + .or_default() + .push((language_server_name.clone(), language.clone())); + this.registration_hooks.register_lsp_adapter( + language.clone(), + ExtensionLspAdapter { + extension: wasm_extension.clone(), + language_server_id: language_server_name.clone(), + language_name: language, + }, + ); + })?; + } + } + + Ok(()) + } + + fn uninstall_extension( + &mut self, + extension_id: &Arc, + cx: &mut ModelContext, + ) -> Task> { + self.loaded_extensions.remove(extension_id); + let languages_to_remove = self + .loaded_languages + .remove(extension_id) + .unwrap_or_default(); + self.registration_hooks + .remove_languages(&languages_to_remove, &[]); + for (language_server_name, language) in self + .loaded_language_servers + .remove(extension_id) + .unwrap_or_default() + { + self.registration_hooks + .remove_lsp_adapter(&language, &language_server_name); + } + + let path = self.extension_dir.join(&extension_id.to_string()); + let fs = self.fs.clone(); + cx.spawn(|_, _| async move { + fs.remove_dir( + &path, + RemoveOptions { + recursive: true, + ignore_if_not_exists: true, + }, + ) + .await + }) + } + + pub fn install_extension( + &mut self, + extension: ExtensionVersion, + tmp_path: PathBuf, + cx: &mut ModelContext, + ) -> Task> { + let path = self.extension_dir.join(&extension.id); + let fs = self.fs.clone(); + + cx.spawn(|this, mut cx| async move { + if fs.is_dir(&path).await { + this.update(&mut cx, |this, cx| { + this.uninstall_extension(&extension.id.clone().into(), cx) + })? + .await?; + } + + fs.rename(&tmp_path, &path, RenameOptions::default()) + .await?; + + Self::load_extension(this, extension, &mut cx).await + }) + } + + pub async fn handle_sync_extensions( + extension_store: Model, + envelope: TypedEnvelope, + mut cx: AsyncAppContext, + ) -> Result { + let requested_extensions = + envelope + .payload + .extensions + .into_iter() + .map(|p| ExtensionVersion { + id: p.id, + version: p.version, + dev: p.dev, + }); + let missing_extensions = extension_store + .update(&mut cx, |extension_store, cx| { + extension_store.sync_extensions(requested_extensions.collect(), cx) + })? + .await?; + + Ok(proto::SyncExtensionsResponse { + missing_extensions: missing_extensions + .into_iter() + .map(|e| proto::Extension { + id: e.id, + version: e.version, + dev: e.dev, + }) + .collect(), + tmp_dir: paths::remote_extensions_uploads_dir() + .to_string_lossy() + .to_string(), + }) + } + + pub async fn handle_install_extension( + extensions: Model, + envelope: TypedEnvelope, + mut cx: AsyncAppContext, + ) -> Result { + let extension = envelope + .payload + .extension + .with_context(|| anyhow!("Invalid InstallExtension request"))?; + + extensions + .update(&mut cx, |extensions, cx| { + extensions.install_extension( + ExtensionVersion { + id: extension.id, + version: extension.version, + dev: extension.dev, + }, + PathBuf::from(envelope.payload.tmp_dir), + cx, + ) + })? + .await?; + + Ok(proto::Ack {}) + } +} + +struct HeadlessRegistrationHooks { + language_registry: Arc, +} + +impl HeadlessRegistrationHooks { + fn new(language_registry: Arc) -> Self { + Self { language_registry } + } +} + +impl ExtensionRegistrationHooks for HeadlessRegistrationHooks { + fn register_language( + &self, + language: LanguageName, + _grammar: Option>, + matcher: language::LanguageMatcher, + load: Arc Result + 'static + Send + Sync>, + ) { + log::info!("registering language: {:?}", language); + self.language_registry + .register_language(language, None, matcher, load) + } + fn register_lsp_adapter(&self, language: LanguageName, adapter: ExtensionLspAdapter) { + log::info!("registering lsp adapter {:?}", language); + self.language_registry + .register_lsp_adapter(language, Arc::new(adapter) as _); + } + + fn register_wasm_grammars(&self, grammars: Vec<(Arc, PathBuf)>) { + self.language_registry.register_wasm_grammars(grammars) + } + + fn remove_lsp_adapter(&self, language: &LanguageName, server_name: &LanguageServerName) { + self.language_registry + .remove_lsp_adapter(language, server_name) + } + + fn remove_languages( + &self, + languages_to_remove: &[LanguageName], + _grammars_to_remove: &[Arc], + ) { + self.language_registry + .remove_languages(languages_to_remove, &[]) + } + + fn update_lsp_status( + &self, + server_name: LanguageServerName, + status: language::LanguageServerBinaryStatus, + ) { + self.language_registry + .update_lsp_status(server_name, status) + } +} diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index dcd62751a7..b9540238f9 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -295,9 +295,13 @@ message Envelope { GetPanicFilesResponse get_panic_files_response = 281; CancelLanguageServerWork cancel_language_server_work = 282; - + LspExtOpenDocs lsp_ext_open_docs = 283; - LspExtOpenDocsResponse lsp_ext_open_docs_response = 284; // current max + LspExtOpenDocsResponse lsp_ext_open_docs_response = 284; + + SyncExtensions sync_extensions = 285; + SyncExtensionsResponse sync_extensions_response = 286; + InstallExtension install_extension = 287; // current max } reserved 87 to 88; @@ -2544,3 +2548,23 @@ message CancelLanguageServerWork { optional string token = 2; } } + +message Extension { + string id = 1; + string version = 2; + bool dev = 3; +} + +message SyncExtensions { + repeated Extension extensions = 1; +} + +message SyncExtensionsResponse { + string tmp_dir = 1; + repeated Extension missing_extensions = 2; +} + +message InstallExtension { + Extension extension = 1; + string tmp_dir = 2; +} diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index 2ec9f8bf55..0810a561b9 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -368,6 +368,9 @@ messages!( (GetPanicFiles, Background), (GetPanicFilesResponse, Background), (CancelLanguageServerWork, Foreground), + (SyncExtensions, Background), + (SyncExtensionsResponse, Background), + (InstallExtension, Background), ); request_messages!( @@ -491,6 +494,8 @@ request_messages!( (GetPathMetadata, GetPathMetadataResponse), (GetPanicFiles, GetPanicFilesResponse), (CancelLanguageServerWork, Ack), + (SyncExtensions, SyncExtensionsResponse), + (InstallExtension, Ack), ); entity_messages!( diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index 827afff7c0..336ced57a8 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -17,6 +17,7 @@ anyhow.workspace = true auto_update.workspace = true release_channel.workspace = true editor.workspace = true +extension_host.workspace = true file_finder.workspace = true futures.workspace = true fuzzy.workspace = true diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index e70b68d374..a9aeacadd8 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -4,6 +4,7 @@ use std::{path::PathBuf, sync::Arc, time::Duration}; use anyhow::{anyhow, Result}; use auto_update::AutoUpdater; use editor::Editor; +use extension_host::ExtensionStore; use futures::channel::oneshot; use gpui::{ percentage, Animation, AnimationExt, AnyWindowHandle, AsyncAppContext, DismissEvent, @@ -630,6 +631,15 @@ pub async fn open_ssh_project( } } + window + .update(cx, |workspace, cx| { + if let Some(client) = workspace.project().read(cx).ssh_client().clone() { + ExtensionStore::global(cx) + .update(cx, |store, cx| store.register_ssh_client(client, cx)); + } + }) + .ok(); + break; } diff --git a/crates/remote/src/ssh_session.rs b/crates/remote/src/ssh_session.rs index 87a58cb050..d8c852c019 100644 --- a/crates/remote/src/ssh_session.rs +++ b/crates/remote/src/ssh_session.rs @@ -1269,6 +1269,7 @@ impl RemoteConnection for SshRemoteConnection { .map(|port| vec!["-P".to_string(), port.to_string()]) .unwrap_or_default(), ) + .arg("-C") .arg("-r") .arg(&src_path) .arg(format!( diff --git a/crates/remote_server/Cargo.toml b/crates/remote_server/Cargo.toml index 73e52895df..d46fb8df56 100644 --- a/crates/remote_server/Cargo.toml +++ b/crates/remote_server/Cargo.toml @@ -29,6 +29,7 @@ chrono.workspace = true clap.workspace = true client.workspace = true env_logger.workspace = true +extension_host.workspace = true fs.workspace = true futures.workspace = true git.workspace = true diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 74416f6ed9..28cd6e115c 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, Result}; +use extension_host::headless_host::HeadlessExtensionStore; use fs::Fs; use gpui::{AppContext, AsyncAppContext, Context as _, Model, ModelContext, PromptLevel}; use http_client::HttpClient; @@ -37,6 +38,7 @@ pub struct HeadlessProject { pub settings_observer: Model, pub next_entry_id: Arc, pub languages: Arc, + pub extensions: Model, } pub struct HeadlessAppState { @@ -147,6 +149,15 @@ impl HeadlessProject { ) .detach(); + let extensions = HeadlessExtensionStore::new( + fs.clone(), + http_client.clone(), + languages.clone(), + paths::remote_extensions_dir().to_path_buf(), + node_runtime, + cx, + ); + let client: AnyProtoClient = session.clone().into(); session.subscribe_to_entity(SSH_PROJECT_ID, &worktree_store); @@ -173,6 +184,15 @@ impl HeadlessProject { client.add_model_request_handler(BufferStore::handle_update_buffer); client.add_model_message_handler(BufferStore::handle_close_buffer); + client.add_request_handler( + extensions.clone().downgrade(), + HeadlessExtensionStore::handle_sync_extensions, + ); + client.add_request_handler( + extensions.clone().downgrade(), + HeadlessExtensionStore::handle_install_extension, + ); + BufferStore::init(&client); WorktreeStore::init(&client); SettingsObserver::init(&client); @@ -190,6 +210,7 @@ impl HeadlessProject { task_store, next_entry_id: Default::default(), languages, + extensions, } } From 37a59d6b2e22e76d1c3c86df9f3ae7c1e8633dee Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 20 Nov 2024 19:21:22 -0700 Subject: [PATCH 088/157] vim: Fix : on welcome screen (#20937) Release Notes: - vim: Fixed `:` on the welcome screen --- assets/keymaps/vim.json | 2 +- crates/welcome/src/welcome.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 10b2009511..1be3e8c9c1 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -577,7 +577,7 @@ } }, { - "context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView", + "context": "EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome", "use_layout_keys": true, "bindings": { ":": "command_palette::Toggle", diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 89f12aa37e..0d1e1c24d1 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -73,6 +73,7 @@ impl Render for WelcomePage { h_flex() .size_full() .bg(cx.theme().colors().editor_background) + .key_context("Welcome") .track_focus(&self.focus_handle(cx)) .child( v_flex() From e062f30d9ea264662137a96f7d769deb8af8670e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 20 Nov 2024 20:29:47 -0700 Subject: [PATCH 089/157] Rename ime_key -> key_char and update behavior (#20953) As part of the recent changes to keyboard support, ime_key is no longer populated by the IME; but instead by the keyboard. As part of #20877 I changed some code to assume that falling back to key was ok, but this was not ok; instead we need to populate this more similarly to how it was done before #20336. The alternative fix could be to instead of simulating these events in our own code to push a fake native event back to the platform input handler. Closes #ISSUE Release Notes: - Fixed a bug where tapping `shift` coudl type "shift" if you had a binding on "shift shift" --- crates/gpui/examples/input.rs | 4 +- crates/gpui/src/platform/keystroke.rs | 37 ++++++++++--------- crates/gpui/src/platform/linux/platform.rs | 6 +-- .../gpui/src/platform/linux/wayland/client.rs | 6 +-- .../gpui/src/platform/linux/wayland/window.rs | 4 +- crates/gpui/src/platform/linux/x11/client.rs | 16 ++++---- crates/gpui/src/platform/linux/x11/window.rs | 4 +- crates/gpui/src/platform/mac/events.rs | 26 +++++++------ crates/gpui/src/platform/mac/window.rs | 21 +++++------ crates/gpui/src/platform/windows/events.rs | 14 +++---- crates/gpui/src/window.rs | 12 ++---- .../markdown_preview/src/markdown_renderer.rs | 2 +- crates/terminal/src/mappings/keys.rs | 2 +- crates/vim/src/digraph.rs | 2 +- 14 files changed, 77 insertions(+), 79 deletions(-) diff --git a/crates/gpui/examples/input.rs b/crates/gpui/examples/input.rs index d52697c43f..29014946cb 100644 --- a/crates/gpui/examples/input.rs +++ b/crates/gpui/examples/input.rs @@ -581,8 +581,8 @@ impl Render for InputExample { format!( "{:} {}", ks.unparse(), - if let Some(ime_key) = ks.ime_key.as_ref() { - format!("-> {:?}", ime_key) + if let Some(key_char) = ks.key_char.as_ref() { + format!("-> {:?}", key_char) } else { "".to_owned() } diff --git a/crates/gpui/src/platform/keystroke.rs b/crates/gpui/src/platform/keystroke.rs index 20a12a691b..af1e5179db 100644 --- a/crates/gpui/src/platform/keystroke.rs +++ b/crates/gpui/src/platform/keystroke.rs @@ -12,14 +12,15 @@ pub struct Keystroke { /// e.g. for option-s, key is "s" pub key: String, - /// ime_key is the character inserted by the IME engine when that key was pressed. - /// e.g. for option-s, ime_key is "ß" - pub ime_key: Option, + /// key_char is the character that could have been typed when + /// this binding was pressed. + /// e.g. for s this is "s", for option-s "ß", and cmd-s None + pub key_char: Option, } impl Keystroke { /// When matching a key we cannot know whether the user intended to type - /// the ime_key or the key itself. On some non-US keyboards keys we use in our + /// the key_char or the key itself. On some non-US keyboards keys we use in our /// bindings are behind option (for example `$` is typed `alt-ç` on a Czech keyboard), /// and on some keyboards the IME handler converts a sequence of keys into a /// specific character (for example `"` is typed as `" space` on a brazilian keyboard). @@ -27,10 +28,10 @@ impl Keystroke { /// This method assumes that `self` was typed and `target' is in the keymap, and checks /// both possibilities for self against the target. pub(crate) fn should_match(&self, target: &Keystroke) -> bool { - if let Some(ime_key) = self - .ime_key + if let Some(key_char) = self + .key_char .as_ref() - .filter(|ime_key| ime_key != &&self.key) + .filter(|key_char| key_char != &&self.key) { let ime_modifiers = Modifiers { control: self.modifiers.control, @@ -38,7 +39,7 @@ impl Keystroke { ..Default::default() }; - if &target.key == ime_key && target.modifiers == ime_modifiers { + if &target.key == key_char && target.modifiers == ime_modifiers { return true; } } @@ -47,9 +48,9 @@ impl Keystroke { } /// key syntax is: - /// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key] - /// ime_key syntax is only used for generating test events, - /// when matching a key with an ime_key set will be matched without it. + /// [ctrl-][alt-][shift-][cmd-][fn-]key[->key_char] + /// key_char syntax is only used for generating test events, + /// when matching a key with an key_char set will be matched without it. pub fn parse(source: &str) -> anyhow::Result { let mut control = false; let mut alt = false; @@ -57,7 +58,7 @@ impl Keystroke { let mut platform = false; let mut function = false; let mut key = None; - let mut ime_key = None; + let mut key_char = None; let mut components = source.split('-').peekable(); while let Some(component) = components.next() { @@ -74,7 +75,7 @@ impl Keystroke { break; } else if next.len() > 1 && next.starts_with('>') { key = Some(String::from(component)); - ime_key = Some(String::from(&next[1..])); + key_char = Some(String::from(&next[1..])); components.next(); } else { return Err(anyhow!("Invalid keystroke `{}`", source)); @@ -118,7 +119,7 @@ impl Keystroke { function, }, key, - ime_key, + key_char: key_char, }) } @@ -154,7 +155,7 @@ impl Keystroke { /// Returns true if this keystroke left /// the ime system in an incomplete state. pub fn is_ime_in_progress(&self) -> bool { - self.ime_key.is_none() + self.key_char.is_none() && (is_printable_key(&self.key) || self.key.is_empty()) && !(self.modifiers.platform || self.modifiers.control @@ -162,17 +163,17 @@ impl Keystroke { || self.modifiers.alt) } - /// Returns a new keystroke with the ime_key filled. + /// Returns a new keystroke with the key_char filled. /// This is used for dispatch_keystroke where we want users to /// be able to simulate typing "space", etc. pub fn with_simulated_ime(mut self) -> Self { - if self.ime_key.is_none() + if self.key_char.is_none() && !self.modifiers.platform && !self.modifiers.control && !self.modifiers.function && !self.modifiers.alt { - self.ime_key = match self.key.as_str() { + self.key_char = match self.key.as_str() { "space" => Some(" ".into()), "tab" => Some("\t".into()), "enter" => Some("\n".into()), diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index f778ebc074..650ed70af8 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -742,14 +742,14 @@ impl Keystroke { } } - // Ignore control characters (and DEL) for the purposes of ime_key - let ime_key = + // Ignore control characters (and DEL) for the purposes of key_char + let key_char = (key_utf32 >= 32 && key_utf32 != 127 && !key_utf8.is_empty()).then_some(key_utf8); Keystroke { modifiers, key, - ime_key, + key_char, } } diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index ab87bb2024..e193201957 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -1208,7 +1208,7 @@ impl Dispatch for WaylandClientStatePtr { compose.feed(keysym); match compose.status() { xkb::Status::Composing => { - keystroke.ime_key = None; + keystroke.key_char = None; state.pre_edit_text = compose.utf8().or(Keystroke::underlying_dead_key(keysym)); let pre_edit = @@ -1220,7 +1220,7 @@ impl Dispatch for WaylandClientStatePtr { xkb::Status::Composed => { state.pre_edit_text.take(); - keystroke.ime_key = compose.utf8(); + keystroke.key_char = compose.utf8(); if let Some(keysym) = compose.keysym() { keystroke.key = xkb::keysym_get_name(keysym); } @@ -1340,7 +1340,7 @@ impl Dispatch for WaylandClientStatePtr { keystroke: Keystroke { modifiers: Modifiers::default(), key: commit_text.clone(), - ime_key: Some(commit_text), + key_char: Some(commit_text), }, is_held: false, })); diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 8d4516b3f3..55ba4f6004 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -687,11 +687,11 @@ impl WaylandWindowStatePtr { } } if let PlatformInput::KeyDown(event) = input { - if let Some(ime_key) = &event.keystroke.ime_key { + if let Some(key_char) = &event.keystroke.key_char { let mut state = self.state.borrow_mut(); if let Some(mut input_handler) = state.input_handler.take() { drop(state); - input_handler.replace_text_in_range(None, ime_key); + input_handler.replace_text_in_range(None, key_char); self.state.borrow_mut().input_handler = Some(input_handler); } } diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 82ef39fc6b..f6c3af0348 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -178,7 +178,7 @@ pub struct X11ClientState { pub(crate) compose_state: Option, pub(crate) pre_edit_text: Option, pub(crate) composing: bool, - pub(crate) pre_ime_key_down: Option, + pub(crate) pre_key_char_down: Option, pub(crate) cursor_handle: cursor::Handle, pub(crate) cursor_styles: HashMap, pub(crate) cursor_cache: HashMap, @@ -446,7 +446,7 @@ impl X11Client { compose_state, pre_edit_text: None, - pre_ime_key_down: None, + pre_key_char_down: None, composing: false, cursor_handle, @@ -858,7 +858,7 @@ impl X11Client { let modifiers = modifiers_from_state(event.state); state.modifiers = modifiers; - state.pre_ime_key_down.take(); + state.pre_key_char_down.take(); let keystroke = { let code = event.detail.into(); let xkb_state = state.previous_xkb_state.clone(); @@ -880,13 +880,13 @@ impl X11Client { match compose_state.status() { xkbc::Status::Composed => { state.pre_edit_text.take(); - keystroke.ime_key = compose_state.utf8(); + keystroke.key_char = compose_state.utf8(); if let Some(keysym) = compose_state.keysym() { keystroke.key = xkbc::keysym_get_name(keysym); } } xkbc::Status::Composing => { - keystroke.ime_key = None; + keystroke.key_char = None; state.pre_edit_text = compose_state .utf8() .or(crate::Keystroke::underlying_dead_key(keysym)); @@ -1156,7 +1156,7 @@ impl X11Client { match event { Event::KeyPress(event) | Event::KeyRelease(event) => { let mut state = self.0.borrow_mut(); - state.pre_ime_key_down = Some(Keystroke::from_xkb( + state.pre_key_char_down = Some(Keystroke::from_xkb( &state.xkb, state.modifiers, event.detail.into(), @@ -1187,11 +1187,11 @@ impl X11Client { fn xim_handle_commit(&self, window: xproto::Window, text: String) -> Option<()> { let window = self.get_window(window).unwrap(); let mut state = self.0.borrow_mut(); - let keystroke = state.pre_ime_key_down.take(); + let keystroke = state.pre_key_char_down.take(); state.composing = false; drop(state); if let Some(mut keystroke) = keystroke { - keystroke.ime_key = Some(text.clone()); + keystroke.key_char = Some(text.clone()); window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent { keystroke, is_held: false, diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index 15712233c2..4df1b50f3f 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -846,9 +846,9 @@ impl X11WindowStatePtr { if let PlatformInput::KeyDown(event) = input { let mut state = self.state.borrow_mut(); if let Some(mut input_handler) = state.input_handler.take() { - if let Some(ime_key) = &event.keystroke.ime_key { + if let Some(key_char) = &event.keystroke.key_char { drop(state); - input_handler.replace_text_in_range(None, ime_key); + input_handler.replace_text_in_range(None, key_char); state = self.state.borrow_mut(); } state.input_handler = Some(input_handler); diff --git a/crates/gpui/src/platform/mac/events.rs b/crates/gpui/src/platform/mac/events.rs index 51716cccb4..f715dba562 100644 --- a/crates/gpui/src/platform/mac/events.rs +++ b/crates/gpui/src/platform/mac/events.rs @@ -245,7 +245,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { .charactersIgnoringModifiers() .to_str() .to_string(); - let mut ime_key = None; + let mut key_char = None; let first_char = characters.chars().next().map(|ch| ch as u16); let modifiers = native_event.modifierFlags(); @@ -261,13 +261,19 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { #[allow(non_upper_case_globals)] let key = match first_char { Some(SPACE_KEY) => { - ime_key = Some(" ".to_string()); + key_char = Some(" ".to_string()); "space".to_string() } + Some(TAB_KEY) => { + key_char = Some("\t".to_string()); + "tab".to_string() + } + Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => { + key_char = Some("\n".to_string()); + "enter".to_string() + } Some(BACKSPACE_KEY) => "backspace".to_string(), - Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter".to_string(), Some(ESCAPE_KEY) => "escape".to_string(), - Some(TAB_KEY) => "tab".to_string(), Some(SHIFT_TAB_KEY) => "tab".to_string(), Some(NSUpArrowFunctionKey) => "up".to_string(), Some(NSDownArrowFunctionKey) => "down".to_string(), @@ -348,7 +354,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { chars_ignoring_modifiers }; - if always_use_cmd_layout || alt { + if !control && !command && !function { let mut mods = NO_MOD; if shift { mods |= SHIFT_MOD; @@ -356,11 +362,9 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { if alt { mods |= OPTION_MOD; } - let alt_key = chars_for_modified_key(native_event.keyCode(), mods); - if alt_key != key { - ime_key = Some(alt_key); - } - }; + + key_char = Some(chars_for_modified_key(native_event.keyCode(), mods)); + } key } @@ -375,7 +379,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { function, }, key, - ime_key, + key_char, } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index e5a04191a3..abb532980a 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1283,18 +1283,17 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: } if event.is_held { - let handled = with_input_handler(&this, |input_handler| { - if !input_handler.apple_press_and_hold_enabled() { - input_handler.replace_text_in_range( - None, - &event.keystroke.ime_key.unwrap_or(event.keystroke.key), - ); + if let Some(key_char) = event.keystroke.key_char.as_ref() { + let handled = with_input_handler(&this, |input_handler| { + if !input_handler.apple_press_and_hold_enabled() { + input_handler.replace_text_in_range(None, &key_char); + return YES; + } + NO + }); + if handled == Some(YES) { return YES; } - NO - }); - if handled == Some(YES) { - return YES; } } @@ -1437,7 +1436,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { let keystroke = Keystroke { modifiers: Default::default(), key: ".".into(), - ime_key: None, + key_char: None, }; let event = PlatformInput::KeyDown(KeyDownEvent { keystroke: keystroke.clone(), diff --git a/crates/gpui/src/platform/windows/events.rs b/crates/gpui/src/platform/windows/events.rs index 92adf6c7cb..5f45d260d9 100644 --- a/crates/gpui/src/platform/windows/events.rs +++ b/crates/gpui/src/platform/windows/events.rs @@ -386,7 +386,7 @@ fn handle_char_msg( return Some(1); }; drop(lock); - let ime_key = keystroke.ime_key.clone(); + let key_char = keystroke.key_char.clone(); let event = KeyDownEvent { keystroke, is_held: lparam.0 & (0x1 << 30) > 0, @@ -397,7 +397,7 @@ fn handle_char_msg( if dispatch_event_result.default_prevented || !dispatch_event_result.propagate { return Some(0); } - let Some(ime_char) = ime_key else { + let Some(ime_char) = key_char else { return Some(1); }; with_input_handler(&state_ptr, |input_handler| { @@ -1172,7 +1172,7 @@ fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option { Some(Keystroke { modifiers, key, - ime_key: None, + key_char: None, }) } @@ -1220,7 +1220,7 @@ fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option { return Some(KeystrokeOrModifier::Keystroke(Keystroke { modifiers, key: format!("f{}", offset + 1), - ime_key: None, + key_char: None, })); }; return None; @@ -1231,7 +1231,7 @@ fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option { Some(KeystrokeOrModifier::Keystroke(Keystroke { modifiers, key, - ime_key: None, + key_char: None, })) } @@ -1253,7 +1253,7 @@ fn parse_char_msg_keystroke(wparam: WPARAM) -> Option { Some(Keystroke { modifiers, key, - ime_key: Some(first_char.to_string()), + key_char: Some(first_char.to_string()), }) } } @@ -1327,7 +1327,7 @@ fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option Some(Keystroke { modifiers, key, - ime_key: None, + key_char: None, }) } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index ec1fd601ec..e4fa74f981 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -3038,7 +3038,7 @@ impl<'a> WindowContext<'a> { return true; } - if let Some(input) = keystroke.with_simulated_ime().ime_key { + if let Some(input) = keystroke.key_char { if let Some(mut input_handler) = self.window.platform_window.take_input_handler() { input_handler.dispatch_input(&input, self); self.window.platform_window.set_input_handler(input_handler); @@ -3267,7 +3267,7 @@ impl<'a> WindowContext<'a> { if let Some(key) = key { keystroke = Some(Keystroke { key: key.to_string(), - ime_key: None, + key_char: None, modifiers: Modifiers::default(), }); } @@ -3482,13 +3482,7 @@ impl<'a> WindowContext<'a> { if !self.propagate_event { continue 'replay; } - if let Some(input) = replay - .keystroke - .with_simulated_ime() - .ime_key - .as_ref() - .cloned() - { + if let Some(input) = replay.keystroke.key_char.as_ref().cloned() { if let Some(mut input_handler) = self.window.platform_window.take_input_handler() { input_handler.dispatch_input(&input, self); self.window.platform_window.set_input_handler(input_handler) diff --git a/crates/markdown_preview/src/markdown_renderer.rs b/crates/markdown_preview/src/markdown_renderer.rs index f38e1c49b5..37ca5636a6 100644 --- a/crates/markdown_preview/src/markdown_renderer.rs +++ b/crates/markdown_preview/src/markdown_renderer.rs @@ -206,7 +206,7 @@ fn render_markdown_list_item( let secondary_modifier = Keystroke { key: "".to_string(), modifiers: Modifiers::secondary_key(), - ime_key: None, + key_char: None, }; Tooltip::text( format!("{}-click to toggle the checkbox", secondary_modifier), diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index 2d4fe4c62e..1efc1f17d2 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -343,7 +343,7 @@ mod test { function: false, }, key: "🖖🏻".to_string(), //2 char string - ime_key: None, + key_char: None, }; assert_eq!(to_esc_str(&ks, &TermMode::NONE, false), None); } diff --git a/crates/vim/src/digraph.rs b/crates/vim/src/digraph.rs index 4c09dd3e33..dcccc8b5cd 100644 --- a/crates/vim/src/digraph.rs +++ b/crates/vim/src/digraph.rs @@ -83,7 +83,7 @@ impl Vim { cx: &mut ViewContext, ) { // handled by handle_literal_input - if keystroke_event.keystroke.ime_key.is_some() { + if keystroke_event.keystroke.key_char.is_some() { return; }; From 7285cdb95541c5b287311bfd9a4ec84bf9d88a10 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 20 Nov 2024 21:24:31 -0700 Subject: [PATCH 090/157] Drop platform lock when setting menu (#20962) Turns out setting the menu (sometimes) calls `selected_range` on the input handler. https://zed-industries.slack.com/archives/C04S6T1T7TQ/p1732160078058279 Release Notes: - Fixed a panic when reloading keymaps --- crates/gpui/src/platform/mac/platform.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index faf9329734..28f427af1b 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -844,7 +844,9 @@ impl Platform for MacPlatform { let app: id = msg_send![APP_CLASS, sharedApplication]; let mut state = self.0.lock(); let actions = &mut state.menu_actions; - app.setMainMenu_(self.create_menu_bar(menus, NSWindow::delegate(app), actions, keymap)); + let menu = self.create_menu_bar(menus, NSWindow::delegate(app), actions, keymap); + drop(state); + app.setMainMenu_(menu); } } From ebaa270bafbd8b4ca504436c5178c68dc81618e7 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 20 Nov 2024 22:04:26 -0700 Subject: [PATCH 091/157] Clip UTF-16 offsets in text for range (#20968) When launching the Pinyin keyboard, macOS will sometimes try to peek one character back in the string. This caused a panic if the preceding character was an emoji. The docs say "don't assume the range is valid", so now we don't. Release Notes: - (macOS) Fixed a panic when using the Pinyin keyboard with emojis --- crates/editor/src/editor.rs | 15 +++++---- crates/gpui/examples/input.rs | 35 +++++++++++++++++++- crates/gpui/src/input.rs | 14 +++++--- crates/gpui/src/platform.rs | 10 ++++-- crates/gpui/src/platform/mac/window.rs | 11 ++++-- crates/terminal_view/src/terminal_element.rs | 1 + 6 files changed, 70 insertions(+), 16 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1435681587..cc450c573f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -14428,15 +14428,16 @@ impl ViewInputHandler for Editor { fn text_for_range( &mut self, range_utf16: Range, + adjusted_range: &mut Option>, cx: &mut ViewContext, ) -> Option { - Some( - self.buffer - .read(cx) - .read(cx) - .text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end)) - .collect(), - ) + let snapshot = self.buffer.read(cx).read(cx); + let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left); + let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right); + if (start.0..end.0) != range_utf16 { + adjusted_range.replace(start.0..end.0); + } + Some(snapshot.text_for_range(start..end).collect()) } fn selected_text_range( diff --git a/crates/gpui/examples/input.rs b/crates/gpui/examples/input.rs index 29014946cb..1a49688a8f 100644 --- a/crates/gpui/examples/input.rs +++ b/crates/gpui/examples/input.rs @@ -15,7 +15,10 @@ actions!( SelectAll, Home, End, - ShowCharacterPalette + ShowCharacterPalette, + Paste, + Cut, + Copy, ] ); @@ -107,6 +110,28 @@ impl TextInput { cx.show_character_palette(); } + fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { + if let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) { + self.replace_text_in_range(None, &text.replace("\n", " "), cx); + } + } + + fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { + if !self.selected_range.is_empty() { + cx.write_to_clipboard(ClipboardItem::new_string( + (&self.content[self.selected_range.clone()]).to_string(), + )); + } + } + fn cut(&mut self, _: &Copy, cx: &mut ViewContext) { + if !self.selected_range.is_empty() { + cx.write_to_clipboard(ClipboardItem::new_string( + (&self.content[self.selected_range.clone()]).to_string(), + )); + self.replace_text_in_range(None, "", cx) + } + } + fn move_to(&mut self, offset: usize, cx: &mut ViewContext) { self.selected_range = offset..offset; cx.notify() @@ -219,9 +244,11 @@ impl ViewInputHandler for TextInput { fn text_for_range( &mut self, range_utf16: Range, + actual_range: &mut Option>, _cx: &mut ViewContext, ) -> Option { let range = self.range_from_utf16(&range_utf16); + actual_range.replace(self.range_to_utf16(&range)); Some(self.content[range].to_string()) } @@ -497,6 +524,9 @@ impl Render for TextInput { .on_action(cx.listener(Self::home)) .on_action(cx.listener(Self::end)) .on_action(cx.listener(Self::show_character_palette)) + .on_action(cx.listener(Self::paste)) + .on_action(cx.listener(Self::cut)) + .on_action(cx.listener(Self::copy)) .on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down)) .on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up)) .on_mouse_up_out(MouseButton::Left, cx.listener(Self::on_mouse_up)) @@ -602,6 +632,9 @@ fn main() { KeyBinding::new("shift-left", SelectLeft, None), KeyBinding::new("shift-right", SelectRight, None), KeyBinding::new("cmd-a", SelectAll, None), + KeyBinding::new("cmd-v", Paste, None), + KeyBinding::new("cmd-c", Copy, None), + KeyBinding::new("cmd-x", Cut, None), KeyBinding::new("home", Home, None), KeyBinding::new("end", End, None), KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, None), diff --git a/crates/gpui/src/input.rs b/crates/gpui/src/input.rs index 161401ecc6..2fb27ac7fc 100644 --- a/crates/gpui/src/input.rs +++ b/crates/gpui/src/input.rs @@ -9,8 +9,12 @@ use std::ops::Range; /// See [`InputHandler`] for details on how to implement each method. pub trait ViewInputHandler: 'static + Sized { /// See [`InputHandler::text_for_range`] for details - fn text_for_range(&mut self, range: Range, cx: &mut ViewContext) - -> Option; + fn text_for_range( + &mut self, + range: Range, + adjusted_range: &mut Option>, + cx: &mut ViewContext, + ) -> Option; /// See [`InputHandler::selected_text_range`] for details fn selected_text_range( @@ -89,10 +93,12 @@ impl InputHandler for ElementInputHandler { fn text_for_range( &mut self, range_utf16: Range, + adjusted_range: &mut Option>, cx: &mut WindowContext, ) -> Option { - self.view - .update(cx, |view, cx| view.text_for_range(range_utf16, cx)) + self.view.update(cx, |view, cx| { + view.text_for_range(range_utf16, adjusted_range, cx) + }) } fn replace_text_in_range( diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index d9016afb68..76a575724f 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -643,9 +643,13 @@ impl PlatformInputHandler { } #[cfg_attr(any(target_os = "linux", target_os = "freebsd"), allow(dead_code))] - fn text_for_range(&mut self, range_utf16: Range) -> Option { + fn text_for_range( + &mut self, + range_utf16: Range, + adjusted: &mut Option>, + ) -> Option { self.cx - .update(|cx| self.handler.text_for_range(range_utf16, cx)) + .update(|cx| self.handler.text_for_range(range_utf16, adjusted, cx)) .ok() .flatten() } @@ -712,6 +716,7 @@ impl PlatformInputHandler { /// A struct representing a selection in a text buffer, in UTF16 characters. /// This is different from a range because the head may be before the tail. +#[derive(Debug)] pub struct UTF16Selection { /// The range of text in the document this selection corresponds to /// in UTF16 characters. @@ -749,6 +754,7 @@ pub trait InputHandler: 'static { fn text_for_range( &mut self, range_utf16: Range, + adjusted_range: &mut Option>, cx: &mut WindowContext, ) -> Option; diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index abb532980a..ce9a4c05bf 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -38,6 +38,7 @@ use std::{ cell::Cell, ffi::{c_void, CStr}, mem, + ops::Range, path::PathBuf, ptr::{self, NonNull}, rc::Rc, @@ -1754,15 +1755,21 @@ extern "C" fn attributed_substring_for_proposed_range( this: &Object, _: Sel, range: NSRange, - _actual_range: *mut c_void, + actual_range: *mut c_void, ) -> id { with_input_handler(this, |input_handler| { let range = range.to_range()?; if range.is_empty() { return None; } + let mut adjusted: Option> = None; - let selected_text = input_handler.text_for_range(range.clone())?; + let selected_text = input_handler.text_for_range(range.clone(), &mut adjusted)?; + if let Some(adjusted) = adjusted { + if adjusted != range { + unsafe { (actual_range as *mut NSRange).write(NSRange::from(adjusted)) }; + } + } unsafe { let string: id = msg_send![class!(NSAttributedString), alloc]; let string: id = msg_send![string, initWithString: ns_string(&selected_text)]; diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index bc4f58a5ef..9d5eb7d410 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -1001,6 +1001,7 @@ impl InputHandler for TerminalInputHandler { fn text_for_range( &mut self, _: std::ops::Range, + _: &mut Option>, _: &mut WindowContext, ) -> Option { None From 6ab4b469845184770b19cf2271b30c51f3823cba Mon Sep 17 00:00:00 2001 From: Adam Richardson <38476863+AdamWRichardson@users.noreply.github.com> Date: Thu, 21 Nov 2024 07:48:13 +0000 Subject: [PATCH 092/157] rope: Minor optimization for tab indices (#20911) This is a follow up on https://github.com/zed-industries/zed/pull/20289 and optimises the tabs by replacing branches with an XOR. I saw this after watching the latest zed decoded episode so thank you for those videos! Release Notes: - N/A --- crates/rope/src/chunk.rs | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/crates/rope/src/chunk.rs b/crates/rope/src/chunk.rs index c158d2429e..5c2b9b87c3 100644 --- a/crates/rope/src/chunk.rs +++ b/crates/rope/src/chunk.rs @@ -504,8 +504,6 @@ impl<'a> ChunkSlice<'a> { #[inline(always)] pub fn tabs(&self) -> Tabs { Tabs { - byte_offset: 0, - char_offset: 0, tabs: self.tabs, chars: self.chars, } @@ -513,8 +511,6 @@ impl<'a> ChunkSlice<'a> { } pub struct Tabs { - byte_offset: usize, - char_offset: usize, tabs: u128, chars: u128, } @@ -536,21 +532,14 @@ impl Iterator for Tabs { let tab_offset = self.tabs.trailing_zeros() as usize; let chars_mask = (1 << tab_offset) - 1; let char_offset = (self.chars & chars_mask).count_ones() as usize; - self.byte_offset += tab_offset; - self.char_offset += char_offset; - let position = TabPosition { - byte_offset: self.byte_offset, - char_offset: self.char_offset, - }; - self.byte_offset += 1; - self.char_offset += 1; - if self.byte_offset == MAX_BASE { - self.tabs = 0; - } else { - self.tabs >>= tab_offset + 1; - self.chars >>= tab_offset + 1; - } + // Since tabs are 1 byte the tab offset is the same as the byte offset + let position = TabPosition { + byte_offset: tab_offset, + char_offset: char_offset, + }; + // Remove the tab we've just seen + self.tabs ^= 1 << tab_offset; Some(position) } From 75c545aa1e7a9cb01febf2f6dc00536c7271ff2f Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:27:25 +0100 Subject: [PATCH 093/157] toolchains: Expose raw JSON representation of a toolchain (#20721) Closes #ISSUE Release Notes: - N/A --- crates/language/src/toolchain.rs | 2 ++ crates/languages/src/python.rs | 3 ++- crates/project/src/toolchain_store.rs | 28 ++++++++++++++++++--------- crates/proto/proto/zed.proto | 1 + crates/workspace/src/persistence.rs | 23 +++++++++++++--------- 5 files changed, 38 insertions(+), 19 deletions(-) diff --git a/crates/language/src/toolchain.rs b/crates/language/src/toolchain.rs index cd9a3bc403..d77690c1f7 100644 --- a/crates/language/src/toolchain.rs +++ b/crates/language/src/toolchain.rs @@ -20,6 +20,8 @@ pub struct Toolchain { pub name: SharedString, pub path: SharedString, pub language_name: LanguageName, + /// Full toolchain data (including language-specific details) + pub as_json: serde_json::Value, } #[async_trait(?Send)] diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index a5fe479627..3db79dd29f 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -591,8 +591,9 @@ impl ToolchainLister for PythonToolchainProvider { .into(); Some(Toolchain { name, - path: toolchain.executable?.to_str()?.to_owned().into(), + path: toolchain.executable.as_ref()?.to_str()?.to_owned().into(), language_name: LanguageName::new("Python"), + as_json: serde_json::to_value(toolchain).ok()?, }) }) .collect(); diff --git a/crates/project/src/toolchain_store.rs b/crates/project/src/toolchain_store.rs index c601ff8f12..4d4c32d745 100644 --- a/crates/project/src/toolchain_store.rs +++ b/crates/project/src/toolchain_store.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{str::FromStr, sync::Arc}; use anyhow::{bail, Result}; @@ -119,6 +119,7 @@ impl ToolchainStore { let toolchain = Toolchain { name: toolchain.name.into(), path: toolchain.path.into(), + as_json: serde_json::Value::from_str(&toolchain.raw_json)?, language_name, }; let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); @@ -144,6 +145,7 @@ impl ToolchainStore { toolchain: toolchain.map(|toolchain| proto::Toolchain { name: toolchain.name.into(), path: toolchain.path.into(), + raw_json: toolchain.as_json.to_string(), }), }) } @@ -182,6 +184,7 @@ impl ToolchainStore { .map(|toolchain| proto::Toolchain { name: toolchain.name.to_string(), path: toolchain.path.to_string(), + raw_json: toolchain.as_json.to_string(), }) .collect::>() } else { @@ -352,6 +355,7 @@ impl RemoteToolchainStore { toolchain: Some(proto::Toolchain { name: toolchain.name.into(), path: toolchain.path.into(), + raw_json: toolchain.as_json.to_string(), }), }) .await @@ -383,10 +387,13 @@ impl RemoteToolchainStore { let toolchains = response .toolchains .into_iter() - .map(|toolchain| Toolchain { - language_name: language_name.clone(), - name: toolchain.name.into(), - path: toolchain.path.into(), + .filter_map(|toolchain| { + Some(Toolchain { + language_name: language_name.clone(), + name: toolchain.name.into(), + path: toolchain.path.into(), + as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?, + }) }) .collect(); let groups = response @@ -421,10 +428,13 @@ impl RemoteToolchainStore { .await .log_err()?; - response.toolchain.map(|toolchain| Toolchain { - language_name: language_name.clone(), - name: toolchain.name.into(), - path: toolchain.path.into(), + response.toolchain.and_then(|toolchain| { + Some(Toolchain { + language_name: language_name.clone(), + name: toolchain.name.into(), + path: toolchain.path.into(), + as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?, + }) }) }) } diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index b9540238f9..178d88ad26 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -2473,6 +2473,7 @@ message ListToolchains { message Toolchain { string name = 1; string path = 2; + string raw_json = 3; } message ToolchainGroup { diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 925d56a921..82de2bc684 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -1,6 +1,6 @@ pub mod model; -use std::path::Path; +use std::{path::Path, str::FromStr}; use anyhow::{anyhow, bail, Context, Result}; use client::DevServerProjectId; @@ -380,6 +380,9 @@ define_connection! { PRIMARY KEY (workspace_id, worktree_id, language_name) ); ), + sql!( + ALTER TABLE toolchains ADD COLUMN raw_json TEXT DEFAULT "{}"; + ), ]; } @@ -1080,18 +1083,19 @@ impl WorkspaceDb { self.write(move |this| { let mut select = this .select_bound(sql!( - SELECT name, path FROM toolchains WHERE workspace_id = ? AND language_name = ? AND worktree_id = ? + SELECT name, path, raw_json FROM toolchains WHERE workspace_id = ? AND language_name = ? AND worktree_id = ? )) .context("Preparing insertion")?; - let toolchain: Vec<(String, String)> = + let toolchain: Vec<(String, String, String)> = select((workspace_id, language_name.0.to_owned(), worktree_id.to_usize()))?; - Ok(toolchain.into_iter().next().map(|(name, path)| Toolchain { + Ok(toolchain.into_iter().next().and_then(|(name, path, raw_json)| Some(Toolchain { name: name.into(), path: path.into(), language_name, - })) + as_json: serde_json::Value::from_str(&raw_json).ok()? + }))) }) .await } @@ -1103,18 +1107,19 @@ impl WorkspaceDb { self.write(move |this| { let mut select = this .select_bound(sql!( - SELECT name, path, worktree_id, language_name FROM toolchains WHERE workspace_id = ? + SELECT name, path, worktree_id, language_name, raw_json FROM toolchains WHERE workspace_id = ? )) .context("Preparing insertion")?; - let toolchain: Vec<(String, String, u64, String)> = + let toolchain: Vec<(String, String, u64, String, String)> = select(workspace_id)?; - Ok(toolchain.into_iter().map(|(name, path, worktree_id, language_name)| (Toolchain { + Ok(toolchain.into_iter().filter_map(|(name, path, worktree_id, language_name, raw_json)| Some((Toolchain { name: name.into(), path: path.into(), language_name: LanguageName::new(&language_name), - }, WorktreeId::from_proto(worktree_id))).collect()) + as_json: serde_json::Value::from_str(&raw_json).ok()? + }, WorktreeId::from_proto(worktree_id)))).collect()) }) .await } From 0b373d43dcc8b25ecd277df7f32773dfccad530b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:57:22 +0100 Subject: [PATCH 094/157] toolchains: Use language-specific terms in UI (#20985) Closes #ISSUE Release Notes: - N/A --- crates/language/src/toolchain.rs | 2 ++ crates/languages/src/python.rs | 18 +++++++++++++--- crates/picker/src/picker.rs | 13 ++++++++++++ crates/project/src/project.rs | 13 ++++++++++++ .../src/active_toolchain.rs | 21 +++++++++++++++---- .../src/toolchain_selector.rs | 18 ++++++++++++++-- 6 files changed, 76 insertions(+), 9 deletions(-) diff --git a/crates/language/src/toolchain.rs b/crates/language/src/toolchain.rs index d77690c1f7..fe8936db08 100644 --- a/crates/language/src/toolchain.rs +++ b/crates/language/src/toolchain.rs @@ -31,6 +31,8 @@ pub trait ToolchainLister: Send + Sync { worktree_root: PathBuf, project_env: Option>, ) -> ToolchainList; + // Returns a term which we should use in UI to refer to a toolchain. + fn term(&self) -> SharedString; } #[async_trait(?Send)] diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 3db79dd29f..df158b9c7d 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -2,8 +2,8 @@ use anyhow::ensure; use anyhow::{anyhow, Result}; use async_trait::async_trait; use collections::HashMap; -use gpui::AsyncAppContext; use gpui::{AppContext, Task}; +use gpui::{AsyncAppContext, SharedString}; use language::language_settings::language_settings; use language::LanguageName; use language::LanguageToolchainStore; @@ -498,8 +498,17 @@ fn python_module_name_from_relative_path(relative_path: &str) -> String { .to_string() } -#[derive(Default)] -pub(crate) struct PythonToolchainProvider {} +pub(crate) struct PythonToolchainProvider { + term: SharedString, +} + +impl Default for PythonToolchainProvider { + fn default() -> Self { + Self { + term: SharedString::new_static("Virtual Environment"), + } + } +} static ENV_PRIORITY_LIST: &'static [PythonEnvironmentKind] = &[ // Prioritize non-Conda environments. @@ -604,6 +613,9 @@ impl ToolchainLister for PythonToolchainProvider { groups: Default::default(), } } + fn term(&self) -> SharedString { + self.term.clone() + } } pub struct EnvironmentApi<'a> { diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 119c412b48..1cdb5af1af 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -425,6 +425,19 @@ impl Picker { self.cancel(&menu::Cancel, cx); } + pub fn refresh_placeholder(&mut self, cx: &mut WindowContext<'_>) { + match &self.head { + Head::Editor(view) => { + let placeholder = self.delegate.placeholder_text(cx); + view.update(cx, |this, cx| { + this.set_placeholder_text(placeholder, cx); + cx.notify(); + }); + } + Head::Empty(_) => {} + } + } + pub fn refresh(&mut self, cx: &mut ViewContext) { let query = self.query(cx); self.update_matches(query, cx); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2b18659b7d..61a700e5d6 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2464,6 +2464,19 @@ impl Project { Task::ready(None) } } + + pub async fn toolchain_term( + languages: Arc, + language_name: LanguageName, + ) -> Option { + languages + .language_for_name(&language_name.0) + .await + .ok()? + .toolchain_lister() + .map(|lister| lister.term()) + } + pub fn activate_toolchain( &self, worktree_id: WorktreeId, diff --git a/crates/toolchain_selector/src/active_toolchain.rs b/crates/toolchain_selector/src/active_toolchain.rs index e2d0b2c808..c49deed02c 100644 --- a/crates/toolchain_selector/src/active_toolchain.rs +++ b/crates/toolchain_selector/src/active_toolchain.rs @@ -4,14 +4,15 @@ use gpui::{ ViewContext, WeakModel, WeakView, }; use language::{Buffer, BufferEvent, LanguageName, Toolchain}; -use project::WorktreeId; -use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, Tooltip}; +use project::{Project, WorktreeId}; +use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, SharedString, Tooltip}; use workspace::{item::ItemHandle, StatusItemView, Workspace}; use crate::ToolchainSelector; pub struct ActiveToolchain { active_toolchain: Option, + term: SharedString, workspace: WeakView, active_buffer: Option<(WorktreeId, WeakModel, Subscription)>, _update_toolchain_task: Task>, @@ -22,6 +23,7 @@ impl ActiveToolchain { Self { active_toolchain: None, active_buffer: None, + term: SharedString::new_static("Toolchain"), workspace: workspace.weak_handle(), _update_toolchain_task: Self::spawn_tracker_task(cx), @@ -44,7 +46,17 @@ impl ActiveToolchain { .update(&mut cx, |this, _| Some(this.language()?.name())) .ok() .flatten()?; - + let term = workspace + .update(&mut cx, |workspace, cx| { + let languages = workspace.project().read(cx).languages(); + Project::toolchain_term(languages.clone(), language_name.clone()) + }) + .ok()? + .await?; + let _ = this.update(&mut cx, |this, cx| { + this.term = term; + cx.notify(); + }); let worktree_id = active_file .update(&mut cx, |this, cx| Some(this.file()?.worktree_id(cx))) .ok() @@ -133,6 +145,7 @@ impl ActiveToolchain { impl Render for ActiveToolchain { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| { + let term = self.term.clone(); el.child( Button::new("change-toolchain", active_toolchain.name.clone()) .label_size(LabelSize::Small) @@ -143,7 +156,7 @@ impl Render for ActiveToolchain { }); } })) - .tooltip(|cx| Tooltip::text("Select Toolchain", cx)), + .tooltip(move |cx| Tooltip::text(format!("Select {}", &term), cx)), ) }) } diff --git a/crates/toolchain_selector/src/toolchain_selector.rs b/crates/toolchain_selector/src/toolchain_selector.rs index 8a3368f816..4c31d600ba 100644 --- a/crates/toolchain_selector/src/toolchain_selector.rs +++ b/crates/toolchain_selector/src/toolchain_selector.rs @@ -126,6 +126,7 @@ pub struct ToolchainSelectorDelegate { workspace: WeakView, worktree_id: WorktreeId, worktree_abs_path_root: Arc, + placeholder_text: Arc, _fetch_candidates_task: Task>, } @@ -144,6 +145,17 @@ impl ToolchainSelectorDelegate { let _fetch_candidates_task = cx.spawn({ let project = project.clone(); move |this, mut cx| async move { + let term = project + .update(&mut cx, |this, _| { + Project::toolchain_term(this.languages().clone(), language_name.clone()) + }) + .ok()? + .await?; + let placeholder_text = format!("Select a {}…", term.to_lowercase()).into(); + let _ = this.update(&mut cx, move |this, cx| { + this.delegate.placeholder_text = placeholder_text; + this.refresh_placeholder(cx); + }); let available_toolchains = project .update(&mut cx, |this, cx| { this.available_toolchains(worktree_id, language_name, cx) @@ -153,6 +165,7 @@ impl ToolchainSelectorDelegate { let _ = this.update(&mut cx, move |this, cx| { this.delegate.candidates = available_toolchains; + if let Some(active_toolchain) = active_toolchain { if let Some(position) = this .delegate @@ -170,7 +183,7 @@ impl ToolchainSelectorDelegate { Some(()) } }); - + let placeholder_text = "Select a toolchain…".to_string().into(); Self { toolchain_selector: language_selector, candidates: Default::default(), @@ -179,6 +192,7 @@ impl ToolchainSelectorDelegate { workspace, worktree_id, worktree_abs_path_root, + placeholder_text, _fetch_candidates_task, } } @@ -196,7 +210,7 @@ impl PickerDelegate for ToolchainSelectorDelegate { type ListItem = ListItem; fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc { - "Select a toolchain...".into() + self.placeholder_text.clone() } fn match_count(&self) -> usize { From 74223c1b009662840de04fde6bfcc9ba4780fddf Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 21 Nov 2024 09:05:00 -0700 Subject: [PATCH 095/157] vim: Fix shortcuts that require shift+punct (#20990) Fixes a bug I introduced in #20953 Release Notes: - N/A --- crates/gpui/src/platform/mac/events.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/gpui/src/platform/mac/events.rs b/crates/gpui/src/platform/mac/events.rs index f715dba562..e1aae9db39 100644 --- a/crates/gpui/src/platform/mac/events.rs +++ b/crates/gpui/src/platform/mac/events.rs @@ -341,6 +341,18 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { chars_ignoring_modifiers = chars_with_cmd; } + if !control && !command && !function { + let mut mods = NO_MOD; + if shift { + mods |= SHIFT_MOD; + } + if alt { + mods |= OPTION_MOD; + } + + key_char = Some(chars_for_modified_key(native_event.keyCode(), mods)); + } + let mut key = if shift && chars_ignoring_modifiers .chars() @@ -354,18 +366,6 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke { chars_ignoring_modifiers }; - if !control && !command && !function { - let mut mods = NO_MOD; - if shift { - mods |= SHIFT_MOD; - } - if alt { - mods |= OPTION_MOD; - } - - key_char = Some(chars_for_modified_key(native_event.keyCode(), mods)); - } - key } }; From 395e25be256b77d5465af015641ead849b766947 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 21 Nov 2024 10:18:54 -0700 Subject: [PATCH 096/157] Fix keybindings on a Spanish ISO keyboard (#20995) Co-Authored-By: Peter Also reformatted the mappings to be easier to read/edit by hand. Release Notes: - Fixed keyboard shortcuts on Spanish ISO keyboards --------- Co-authored-by: Peter --- crates/settings/src/key_equivalents.rs | 1520 +++++++++++++++++++++--- 1 file changed, 1370 insertions(+), 150 deletions(-) diff --git a/crates/settings/src/key_equivalents.rs b/crates/settings/src/key_equivalents.rs index 1c68f48db4..4c5ae9e065 100644 --- a/crates/settings/src/key_equivalents.rs +++ b/crates/settings/src/key_equivalents.rs @@ -26,157 +26,1377 @@ use collections::HashMap; // From there I used multi-cursor to produce this match statement. #[cfg(target_os = "macos")] pub fn get_key_equivalents(layout: &str) -> Option> { - let (from, to) = match layout { - "com.apple.keylayout.Welsh" => ("#", "£"), - "com.apple.keylayout.Turkmen" => ("qc]Q`|[XV\\^v~Cx}{", "äçöÄžŞňÜÝş№ýŽÇüÖŇ"), - "com.apple.keylayout.Turkish-QWERTY-PC" => ( - "$\\|`'[}^=.#{*+:/~;)(@<,&]>\"", - "+,;<ığÜ&.ç^Ğ(:Ş*>ş=)'Öö/üÇI", - ), - "com.apple.keylayout.Sami-PC" => ( - "}*x\"w[~^/@`]{|<)>W(\\X=Qq&':;", - "Æ(čŊšøŽ&´\"žæØĐ;=:Š)đČ`Áá/ŋÅå", - ), - "com.apple.keylayout.LatinAmerican" => { - ("[^~>`(<\\@{;*&/):]|='}\"", "{&>:<);¿\"[ñ(/'=Ñ}¡*´]¨") - } - "com.apple.keylayout.IrishExtended" => ("#", "£"), - "com.apple.keylayout.Icelandic" => ("[}=:/'){(*&;^|`\"\\>]<~@", "æ´*Ð'ö=Æ)(/ð&Þ<Öþ:´;>\""), - "com.apple.keylayout.German-DIN-2137" => { - ("}~/<^>{`:\\)&=[]@|;#'\"(*", "Ä>ß;&:Ö<Ü#=/*öä\"'ü§´`)(") - } - "com.apple.keylayout.FinnishSami-PC" => { - (")=*\"\\[@{:>';/<|~(]}^`&", "=`(ˆ@ö\"ÖÅ:¨å´;*>)äÄ& { - ("];{`:'*<~=/}\\|&[\"($^)>@", "äåÖ<Ũ(;>`´Ä'*/öˆ)€&=:\"") - } - "com.apple.keylayout.Faroese" => ("}\";/$>^@~`:&[*){|]=(\\<'", "ÐØæ´€:&\"><Æ/å(=Å*ð`)';ø"), - "com.apple.keylayout.Croatian-PC" => { - ("{@~;<=>(&*['|]\":/}^`)\\", "Š\">č;*:)/(šćŽđĆČ'Đ&<=ž") - } - "com.apple.keylayout.Croatian" => ("{@;<~=>(&*['|]\":}^)\\`", "Š\"č;>*:)'(šćŽđĆČĐ&=ž<"), - "com.apple.keylayout.Azeri" => (":{W?./\"[}<]|,>';w", "IÖÜ,ş.ƏöĞÇğ/çŞəıü"), - "com.apple.keylayout.Albanian" => ("\\'~;:|<>`\"@", "ë@>çÇË;:<'\""), - "com.apple.keylayout.SwissFrench" => ( - ":@&'~^)$;\"][\\/#={!|*+`<(>}", - "ü\"/^>&=çè`àé$'*¨ö+£(!<;):ä", - ), - "com.apple.keylayout.Swedish" => ("(]\\\"~$`^{|/>*:;<)&=[}'@", ")ä'^>€<&Ö*´:(Åå;=/`öĨ\""), - "com.apple.keylayout.Swedish-Pro" => { - ("/^*`'{|)$>&<[\\;(~\"}@]:=", "´&(<¨Ö*=€:/;ö'å)>^Ä\"äÅ`") - } - "com.apple.keylayout.Spanish" => ("|!\\<{[:;@`/~].'>}\"^", "\"¡'¿Ññº´!<.>;ç`Ç:¨/"), - "com.apple.keylayout.Spanish-ISO" => ( - "|~`]/:)(<&^>*;#}\"{.\\['@", - "\"><;.º=)¿/&Ç(´·not found¨Ñç'ñ`\"", - ), - "com.apple.keylayout.Portuguese" => (")`/'^\"<];>[:{@}(&*=~", "=<'´&`;~º:çªÇ\"^)/(*>"), - "com.apple.keylayout.Italian" => ( - "*7};8:!5%(1&4]^\\6)32>.à32", - ), - "com.apple.keylayout.Italian-Pro" => { - ("/:@[]'\\=){;|#<\"(*^&`}>~", "'é\"òàìù*=çè§£;^)(&/<°:>") - } - "com.apple.keylayout.Irish" => ("#", "£"), - "com.apple.keylayout.German" => ("=`#'}:)/\"^&]*{;|[<(>~@\\", "*<§´ÄÜ=ß`&/ä(Öü'ö;):>\"#"), - "com.apple.keylayout.French" => ( - "*}7;8:!5%(1&4]\\^6)32>.ç32", - ), - "com.apple.keylayout.French-numerical" => ( - "|!52;][>&@\"%'{)<~7.1/^(}*8#0$9`6\\3:4", - "£1(é)$^/72%5ù¨0.>è;&:69*8!3à4ç<§`\"°'", - ), - "com.apple.keylayout.French-PC" => ( - "!&\"_$}/72>8]#:31)*<%4;6\\-{['@(0|5.`9~^", - "17%°4£:èé/_$3§\"&08.5'!-*)¨^ù29àμ(;<ç>6", - ), - "com.apple.keylayout.Finnish" => ("/^*`)'{|$>&<[\\~;(\"}@]:=", "´&(<=¨Ö*€:/;ö'>å)^Ä\"äÅ`"), - "com.apple.keylayout.Danish" => ("=[;'`{}|>]*^(&@~)<\\/$\":", "`æå¨<ÆØ*:ø(&)/\">=;'´€^Å"), - "com.apple.keylayout.Canadian-CSA" => ("\\?']/><[{}|~`\"", "àÉèçé\"'^¨ÇÀÙùÈ"), - "com.apple.keylayout.British" => ("#", "£"), - "com.apple.keylayout.Brazilian-ABNT2" => ("\"|~?`'/^\\", "`^\"Ç'´ç¨~"), - "com.apple.keylayout.Belgian" => ( - "`3/*<\\8>7#&96@);024(|'1\":$[~5.%^}]{!", - "<\":8.`!/è37ç§20)àé'9£ù&%°4^>(;56*$¨1", - ), - "com.apple.keylayout.Austrian" => ("/^*`'{|)>&<[\\;(~\"}@]:=#", "ß&(<´Ö'=:/;ö#ü)>`Ä\"äÜ*§"), - "com.apple.keylayout.Slovak-QWERTY" => ( - "):9;63'\"]^/+@~>`? ( - "!$`10&:#4^*~{%5')}6/\"[8]97?;<@23>(+", - "14ň+é7\"3č68ŇÚ5ť§0Äž'!úáäíýˇô?2ľš:9%", - ), - "com.apple.keylayout.Polish" => ( - "&)|?,%:;^}]_{!+#(*`/[~<\"$.>'@=\\", - ":\"$Ż.+Łł=)(ćź§]!/_<żó>śę?,ńą%[;", - ), - "com.apple.keylayout.Lithuanian" => ("+#&=!%1*@73^584$26", "ŽĘŲžĄĮąŪČųęŠįūėĖčš"), - "com.apple.keylayout.Hungarian" => ( - "}(*@\"{=/|;>'[`<~\\!$&0#:]^)+", - "Ú)(\"ÁŐóüŰé:áőíÜÍű'!=ö+Éú/ÖÓ", - ), - "com.apple.keylayout.Hungarian-QWERTY" => ( - "=]#>@/&<`0')~(\\!:*;$\"+^{|}[", - "óú+:\"ü=ÜíöáÖÍ)ű'É(é!ÁÓ/ŐŰÚő", - ), - "com.apple.keylayout.Czech-QWERTY" => ( - "9>0[2()\"}@]46%5;#8{*7^~+!3?&'<$/1`:", - "í:éúě90!(2)čž5řů3áÚ8ý6`%1šˇ7§?4'+¨\"", - ), - "com.apple.keylayout.Maltese" => ("[`}{#]~", "ġżĦĠ£ħŻ"), - "com.apple.keylayout.Turkish" => ( - "|}(#>&^-/`$%@]~*,[\"<_.{:'\\)", - "ÜI%\"Ç)/ş.<'(*ı>_öğ-ÖŞçĞ$,ü:", - ), - "com.apple.keylayout.Turkish-Standard" => { - ("|}(#>=&^`@]~*,;[\"<.{:'\\)", "ÜI)^;*'&ö\"ıÖ(.çğŞ:,ĞÇşü=") - } - "com.apple.keylayout.NorwegianSami-PC" => { - ("\"}~<`&>':{@*^|\\)=([]/;", "ˆÆ>; { - (";\\@>&'<]\"|(=}^)`[~:*{", "čž\":'ć;đĆŽ)*Đ&=<š>Č(Š") - } - "com.apple.keylayout.Slovenian" => ("]`^@)&\":'*=<{;}(~>\\|[", "đ<&\"='ĆČć(*;ŠčĐ)>:žŽš"), - "com.apple.keylayout.SwedishSami-PC" => { - ("@=<^|`>){'&\"}]~[/:*\\(;", "\"`;&*<:=Ö¨/ˆÄä>ö´Å(@)å") - } - "com.apple.keylayout.SwissGerman" => ( - "={#:\\}!(+]/<\";$'`*[>&^~@)|", - "¨é*è$à+)!ä';`üç^<(ö:/&>\"=£", - ), - "com.apple.keylayout.Hawaiian" => ("'", "ʻ"), - "com.apple.keylayout.NorthernSami" => ( - ":/[<{X\"wQx\\(;~>W}`*@])'^|=q&", - "Å´ø;ØČŊšÁčđ)åŽ:ŠÆž(\"æ=ŋ&Đ`á/", - ), - "com.apple.keylayout.USInternational-PC" => ("^~", "ˆ˜"), - "com.apple.keylayout.NorwegianExtended" => ("^~", "ˆ˜"), - "com.apple.keylayout.Norwegian" => ("`'~\"\\*|=/@)[:}&><]{(^;", "<¨>^@(*`´\"=øÅÆ/:;æØ)&å"), - "com.apple.keylayout.ABC-QWERTZ" => { - ("\"}~<`>'&#:{@*^|\\)=(]/;[", "`Ä>;<:´/§ÜÖ\"(&'#=*)äßüö") - } - "com.apple.keylayout.ABC-AZERTY" => ( - ">[$61%@7|)&8\":}593(.4^8:ùà", - ), - "com.apple.keylayout.Czech" => ( - "(7*#193620?/{)@~!$8+;:%4\">`^]&5}[<'", - "9ý83+íšžěéˇ'Ú02`14á%ů\"5č!:¨6)7ř(ú?§", - ), - "com.apple.keylayout.Brazilian-Pro" => ("^~", "ˆ˜"), - _ => { - return None; - } - }; - debug_assert!(from.chars().count() == to.chars().count()); + let mappings: &[(char, char)] = match layout { + "com.apple.keylayout.ABC-AZERTY" => &[ + ('!', '1'), + ('"', '%'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('.', ';'), + ('/', ':'), + ('0', 'à'), + ('1', '&'), + ('2', 'é'), + ('3', '"'), + ('4', '\''), + ('5', '('), + ('6', '§'), + ('7', 'è'), + ('8', '!'), + ('9', 'ç'), + (':', '°'), + (';', ')'), + ('<', '.'), + ('>', '/'), + ('@', '2'), + ('[', '^'), + ('\'', 'ù'), + ('\\', '`'), + (']', '$'), + ('^', '6'), + ('`', '<'), + ('{', '¨'), + ('|', '£'), + ('}', '*'), + ('~', '>'), + ], + "com.apple.keylayout.ABC-QWERTZ" => &[ + ('"', '`'), + ('#', '§'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', 'ß'), + (':', 'Ü'), + (';', 'ü'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '´'), + ('\\', '#'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '\''), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.Albanian" => &[ + ('"', '\''), + (':', 'Ç'), + (';', 'ç'), + ('<', ';'), + ('>', ':'), + ('@', '"'), + ('\'', '@'), + ('\\', 'ë'), + ('`', '<'), + ('|', 'Ë'), + ('~', '>'), + ], + "com.apple.keylayout.Austrian" => &[ + ('"', '`'), + ('#', '§'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', 'ß'), + (':', 'Ü'), + (';', 'ü'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '´'), + ('\\', '#'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '\''), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.Azeri" => &[ + ('"', 'Ə'), + (',', 'ç'), + ('.', 'ş'), + ('/', '.'), + (':', 'I'), + (';', 'ı'), + ('<', 'Ç'), + ('>', 'Ş'), + ('?', ','), + ('W', 'Ü'), + ('[', 'ö'), + ('\'', 'ə'), + (']', 'ğ'), + ('w', 'ü'), + ('{', 'Ö'), + ('|', '/'), + ('}', 'Ğ'), + ], + "com.apple.keylayout.Belgian" => &[ + ('!', '1'), + ('"', '%'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('.', ';'), + ('/', ':'), + ('0', 'à'), + ('1', '&'), + ('2', 'é'), + ('3', '"'), + ('4', '\''), + ('5', '('), + ('6', '§'), + ('7', 'è'), + ('8', '!'), + ('9', 'ç'), + (':', '°'), + (';', ')'), + ('<', '.'), + ('>', '/'), + ('@', '2'), + ('[', '^'), + ('\'', 'ù'), + ('\\', '`'), + (']', '$'), + ('^', '6'), + ('`', '<'), + ('{', '¨'), + ('|', '£'), + ('}', '*'), + ('~', '>'), + ], + "com.apple.keylayout.Brazilian-ABNT2" => &[ + ('"', '`'), + ('/', 'ç'), + ('?', 'Ç'), + ('\'', '´'), + ('\\', '~'), + ('^', '¨'), + ('`', '\''), + ('|', '^'), + ('~', '"'), + ], + "com.apple.keylayout.Brazilian-Pro" => &[('^', 'ˆ'), ('~', '˜')], + "com.apple.keylayout.British" => &[('#', '£')], + "com.apple.keylayout.Canadian-CSA" => &[ + ('"', 'È'), + ('/', 'é'), + ('<', '\''), + ('>', '"'), + ('?', 'É'), + ('[', '^'), + ('\'', 'è'), + ('\\', 'à'), + (']', 'ç'), + ('`', 'ù'), + ('{', '¨'), + ('|', 'À'), + ('}', 'Ç'), + ('~', 'Ù'), + ], + "com.apple.keylayout.Croatian" => &[ + ('"', 'Ć'), + ('&', '\''), + ('(', ')'), + (')', '='), + ('*', '('), + (':', 'Č'), + (';', 'č'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'š'), + ('\'', 'ć'), + ('\\', 'ž'), + (']', 'đ'), + ('^', '&'), + ('`', '<'), + ('{', 'Š'), + ('|', 'Ž'), + ('}', 'Đ'), + ('~', '>'), + ], + "com.apple.keylayout.Croatian-PC" => &[ + ('"', 'Ć'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '\''), + (':', 'Č'), + (';', 'č'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'š'), + ('\'', 'ć'), + ('\\', 'ž'), + (']', 'đ'), + ('^', '&'), + ('`', '<'), + ('{', 'Š'), + ('|', 'Ž'), + ('}', 'Đ'), + ('~', '>'), + ], + "com.apple.keylayout.Czech" => &[ + ('!', '1'), + ('"', '!'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('+', '%'), + ('/', '\''), + ('0', 'é'), + ('1', '+'), + ('2', 'ě'), + ('3', 'š'), + ('4', 'č'), + ('5', 'ř'), + ('6', 'ž'), + ('7', 'ý'), + ('8', 'á'), + ('9', 'í'), + (':', '"'), + (';', 'ů'), + ('<', '?'), + ('>', ':'), + ('?', 'ˇ'), + ('@', '2'), + ('[', 'ú'), + ('\'', '§'), + (']', ')'), + ('^', '6'), + ('`', '¨'), + ('{', 'Ú'), + ('}', '('), + ('~', '`'), + ], + "com.apple.keylayout.Czech-QWERTY" => &[ + ('!', '1'), + ('"', '!'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('+', '%'), + ('/', '\''), + ('0', 'é'), + ('1', '+'), + ('2', 'ě'), + ('3', 'š'), + ('4', 'č'), + ('5', 'ř'), + ('6', 'ž'), + ('7', 'ý'), + ('8', 'á'), + ('9', 'í'), + (':', '"'), + (';', 'ů'), + ('<', '?'), + ('>', ':'), + ('?', 'ˇ'), + ('@', '2'), + ('[', 'ú'), + ('\'', '§'), + (']', ')'), + ('^', '6'), + ('`', '¨'), + ('{', 'Ú'), + ('}', '('), + ('~', '`'), + ], + "com.apple.keylayout.Danish" => &[ + ('"', '^'), + ('$', '€'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'æ'), + ('\'', '¨'), + ('\\', '\''), + (']', 'ø'), + ('^', '&'), + ('`', '<'), + ('{', 'Æ'), + ('|', '*'), + ('}', 'Ø'), + ('~', '>'), + ], + "com.apple.keylayout.Faroese" => &[ + ('"', 'Ø'), + ('$', '€'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Æ'), + (';', 'æ'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'å'), + ('\'', 'ø'), + ('\\', '\''), + (']', 'ð'), + ('^', '&'), + ('`', '<'), + ('{', 'Å'), + ('|', '*'), + ('}', 'Ð'), + ('~', '>'), + ], + "com.apple.keylayout.Finnish" => &[ + ('"', '^'), + ('$', '€'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '¨'), + ('\\', '\''), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '*'), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.FinnishExtended" => &[ + ('"', 'ˆ'), + ('$', '€'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '¨'), + ('\\', '\''), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '*'), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.FinnishSami-PC" => &[ + ('"', 'ˆ'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '¨'), + ('\\', '@'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '*'), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.French" => &[ + ('!', '1'), + ('"', '%'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('.', ';'), + ('/', ':'), + ('0', 'à'), + ('1', '&'), + ('2', 'é'), + ('3', '"'), + ('4', '\''), + ('5', '('), + ('6', '§'), + ('7', 'è'), + ('8', '!'), + ('9', 'ç'), + (':', '°'), + (';', ')'), + ('<', '.'), + ('>', '/'), + ('@', '2'), + ('[', '^'), + ('\'', 'ù'), + ('\\', '`'), + (']', '$'), + ('^', '6'), + ('`', '<'), + ('{', '¨'), + ('|', '£'), + ('}', '*'), + ('~', '>'), + ], + "com.apple.keylayout.French-PC" => &[ + ('!', '1'), + ('"', '%'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('-', ')'), + ('.', ';'), + ('/', ':'), + ('0', 'à'), + ('1', '&'), + ('2', 'é'), + ('3', '"'), + ('4', '\''), + ('5', '('), + ('6', '-'), + ('7', 'è'), + ('8', '_'), + ('9', 'ç'), + (':', '§'), + (';', '!'), + ('<', '.'), + ('>', '/'), + ('@', '2'), + ('[', '^'), + ('\'', 'ù'), + ('\\', '*'), + (']', '$'), + ('^', '6'), + ('_', '°'), + ('`', '<'), + ('{', '¨'), + ('|', 'μ'), + ('}', '£'), + ('~', '>'), + ], + "com.apple.keylayout.French-numerical" => &[ + ('!', '1'), + ('"', '%'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('.', ';'), + ('/', ':'), + ('0', 'à'), + ('1', '&'), + ('2', 'é'), + ('3', '"'), + ('4', '\''), + ('5', '('), + ('6', '§'), + ('7', 'è'), + ('8', '!'), + ('9', 'ç'), + (':', '°'), + (';', ')'), + ('<', '.'), + ('>', '/'), + ('@', '2'), + ('[', '^'), + ('\'', 'ù'), + ('\\', '`'), + (']', '$'), + ('^', '6'), + ('`', '<'), + ('{', '¨'), + ('|', '£'), + ('}', '*'), + ('~', '>'), + ], + "com.apple.keylayout.German" => &[ + ('"', '`'), + ('#', '§'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', 'ß'), + (':', 'Ü'), + (';', 'ü'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '´'), + ('\\', '#'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '\''), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.German-DIN-2137" => &[ + ('"', '`'), + ('#', '§'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', 'ß'), + (':', 'Ü'), + (';', 'ü'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '´'), + ('\\', '#'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '\''), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.Hawaiian" => &[('\'', 'ʻ')], + "com.apple.keylayout.Hungarian" => &[ + ('!', '\''), + ('"', 'Á'), + ('#', '+'), + ('$', '!'), + ('&', '='), + ('(', ')'), + (')', 'Ö'), + ('*', '('), + ('+', 'Ó'), + ('/', 'ü'), + ('0', 'ö'), + (':', 'É'), + (';', 'é'), + ('<', 'Ü'), + ('=', 'ó'), + ('>', ':'), + ('@', '"'), + ('[', 'ő'), + ('\'', 'á'), + ('\\', 'ű'), + (']', 'ú'), + ('^', '/'), + ('`', 'í'), + ('{', 'Ő'), + ('|', 'Ű'), + ('}', 'Ú'), + ('~', 'Í'), + ], + "com.apple.keylayout.Hungarian-QWERTY" => &[ + ('!', '\''), + ('"', 'Á'), + ('#', '+'), + ('$', '!'), + ('&', '='), + ('(', ')'), + (')', 'Ö'), + ('*', '('), + ('+', 'Ó'), + ('/', 'ü'), + ('0', 'ö'), + (':', 'É'), + (';', 'é'), + ('<', 'Ü'), + ('=', 'ó'), + ('>', ':'), + ('@', '"'), + ('[', 'ő'), + ('\'', 'á'), + ('\\', 'ű'), + (']', 'ú'), + ('^', '/'), + ('`', 'í'), + ('{', 'Ő'), + ('|', 'Ű'), + ('}', 'Ú'), + ('~', 'Í'), + ], + "com.apple.keylayout.Icelandic" => &[ + ('"', 'Ö'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '\''), + (':', 'Ð'), + (';', 'ð'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'æ'), + ('\'', 'ö'), + ('\\', 'þ'), + (']', '´'), + ('^', '&'), + ('`', '<'), + ('{', 'Æ'), + ('|', 'Þ'), + ('}', '´'), + ('~', '>'), + ], + "com.apple.keylayout.Irish" => &[('#', '£')], + "com.apple.keylayout.IrishExtended" => &[('#', '£')], + "com.apple.keylayout.Italian" => &[ + ('!', '1'), + ('"', '%'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + (',', ';'), + ('.', ':'), + ('/', ','), + ('0', 'é'), + ('1', '&'), + ('2', '"'), + ('3', '\''), + ('4', '('), + ('5', 'ç'), + ('6', 'è'), + ('7', ')'), + ('8', '£'), + ('9', 'à'), + (':', '!'), + (';', 'ò'), + ('<', '.'), + ('>', '/'), + ('@', '2'), + ('[', 'ì'), + ('\'', 'ù'), + ('\\', '§'), + (']', '$'), + ('^', '6'), + ('`', '<'), + ('{', '^'), + ('|', '°'), + ('}', '*'), + ('~', '>'), + ], + "com.apple.keylayout.Italian-Pro" => &[ + ('"', '^'), + ('#', '£'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '\''), + (':', 'é'), + (';', 'è'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'ò'), + ('\'', 'ì'), + ('\\', 'ù'), + (']', 'à'), + ('^', '&'), + ('`', '<'), + ('{', 'ç'), + ('|', '§'), + ('}', '°'), + ('~', '>'), + ], + "com.apple.keylayout.LatinAmerican" => &[ + ('"', '¨'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '\''), + (':', 'Ñ'), + (';', 'ñ'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', '{'), + ('\'', '´'), + ('\\', '¿'), + (']', '}'), + ('^', '&'), + ('`', '<'), + ('{', '['), + ('|', '¡'), + ('}', ']'), + ('~', '>'), + ], + "com.apple.keylayout.Lithuanian" => &[ + ('!', 'Ą'), + ('#', 'Ę'), + ('$', 'Ė'), + ('%', 'Į'), + ('&', 'Ų'), + ('*', 'Ū'), + ('+', 'Ž'), + ('1', 'ą'), + ('2', 'č'), + ('3', 'ę'), + ('4', 'ė'), + ('5', 'į'), + ('6', 'š'), + ('7', 'ų'), + ('8', 'ū'), + ('=', 'ž'), + ('@', 'Č'), + ('^', 'Š'), + ], + "com.apple.keylayout.Maltese" => &[ + ('#', '£'), + ('[', 'ġ'), + (']', 'ħ'), + ('`', 'ż'), + ('{', 'Ġ'), + ('}', 'Ħ'), + ('~', 'Ż'), + ], + "com.apple.keylayout.NorthernSami" => &[ + ('"', 'Ŋ'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('Q', 'Á'), + ('W', 'Š'), + ('X', 'Č'), + ('[', 'ø'), + ('\'', 'ŋ'), + ('\\', 'đ'), + (']', 'æ'), + ('^', '&'), + ('`', 'ž'), + ('q', 'á'), + ('w', 'š'), + ('x', 'č'), + ('{', 'Ø'), + ('|', 'Đ'), + ('}', 'Æ'), + ('~', 'Ž'), + ], + "com.apple.keylayout.Norwegian" => &[ + ('"', '^'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ø'), + ('\'', '¨'), + ('\\', '@'), + (']', 'æ'), + ('^', '&'), + ('`', '<'), + ('{', 'Ø'), + ('|', '*'), + ('}', 'Æ'), + ('~', '>'), + ], + "com.apple.keylayout.NorwegianExtended" => &[('^', 'ˆ'), ('~', '˜')], + "com.apple.keylayout.NorwegianSami-PC" => &[ + ('"', 'ˆ'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ø'), + ('\'', '¨'), + ('\\', '@'), + (']', 'æ'), + ('^', '&'), + ('`', '<'), + ('{', 'Ø'), + ('|', '*'), + ('}', 'Æ'), + ('~', '>'), + ], + "com.apple.keylayout.Polish" => &[ + ('!', '§'), + ('"', 'ę'), + ('#', '!'), + ('$', '?'), + ('%', '+'), + ('&', ':'), + ('(', '/'), + (')', '"'), + ('*', '_'), + ('+', ']'), + (',', '.'), + ('.', ','), + ('/', 'ż'), + (':', 'Ł'), + (';', 'ł'), + ('<', 'ś'), + ('=', '['), + ('>', 'ń'), + ('?', 'Ż'), + ('@', '%'), + ('[', 'ó'), + ('\'', 'ą'), + ('\\', ';'), + (']', '('), + ('^', '='), + ('_', 'ć'), + ('`', '<'), + ('{', 'ź'), + ('|', '$'), + ('}', ')'), + ('~', '>'), + ], + "com.apple.keylayout.Portuguese" => &[ + ('"', '`'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '\''), + (':', 'ª'), + (';', 'º'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'ç'), + ('\'', '´'), + (']', '~'), + ('^', '&'), + ('`', '<'), + ('{', 'Ç'), + ('}', '^'), + ('~', '>'), + ], + "com.apple.keylayout.Sami-PC" => &[ + ('"', 'Ŋ'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('Q', 'Á'), + ('W', 'Š'), + ('X', 'Č'), + ('[', 'ø'), + ('\'', 'ŋ'), + ('\\', 'đ'), + (']', 'æ'), + ('^', '&'), + ('`', 'ž'), + ('q', 'á'), + ('w', 'š'), + ('x', 'č'), + ('{', 'Ø'), + ('|', 'Đ'), + ('}', 'Æ'), + ('~', 'Ž'), + ], + "com.apple.keylayout.Serbian-Latin" => &[ + ('"', 'Ć'), + ('&', '\''), + ('(', ')'), + (')', '='), + ('*', '('), + (':', 'Č'), + (';', 'č'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'š'), + ('\'', 'ć'), + ('\\', 'ž'), + (']', 'đ'), + ('^', '&'), + ('`', '<'), + ('{', 'Š'), + ('|', 'Ž'), + ('}', 'Đ'), + ('~', '>'), + ], + "com.apple.keylayout.Slovak" => &[ + ('!', '1'), + ('"', '!'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('+', '%'), + ('/', '\''), + ('0', 'é'), + ('1', '+'), + ('2', 'ľ'), + ('3', 'š'), + ('4', 'č'), + ('5', 'ť'), + ('6', 'ž'), + ('7', 'ý'), + ('8', 'á'), + ('9', 'í'), + (':', '"'), + (';', 'ô'), + ('<', '?'), + ('>', ':'), + ('?', 'ˇ'), + ('@', '2'), + ('[', 'ú'), + ('\'', '§'), + (']', 'ä'), + ('^', '6'), + ('`', 'ň'), + ('{', 'Ú'), + ('}', 'Ä'), + ('~', 'Ň'), + ], + "com.apple.keylayout.Slovak-QWERTY" => &[ + ('!', '1'), + ('"', '!'), + ('#', '3'), + ('$', '4'), + ('%', '5'), + ('&', '7'), + ('(', '9'), + (')', '0'), + ('*', '8'), + ('+', '%'), + ('/', '\''), + ('0', 'é'), + ('1', '+'), + ('2', 'ľ'), + ('3', 'š'), + ('4', 'č'), + ('5', 'ť'), + ('6', 'ž'), + ('7', 'ý'), + ('8', 'á'), + ('9', 'í'), + (':', '"'), + (';', 'ô'), + ('<', '?'), + ('>', ':'), + ('?', 'ˇ'), + ('@', '2'), + ('[', 'ú'), + ('\'', '§'), + (']', 'ä'), + ('^', '6'), + ('`', 'ň'), + ('{', 'Ú'), + ('}', 'Ä'), + ('~', 'Ň'), + ], + "com.apple.keylayout.Slovenian" => &[ + ('"', 'Ć'), + ('&', '\''), + ('(', ')'), + (')', '='), + ('*', '('), + (':', 'Č'), + (';', 'č'), + ('<', ';'), + ('=', '*'), + ('>', ':'), + ('@', '"'), + ('[', 'š'), + ('\'', 'ć'), + ('\\', 'ž'), + (']', 'đ'), + ('^', '&'), + ('`', '<'), + ('{', 'Š'), + ('|', 'Ž'), + ('}', 'Đ'), + ('~', '>'), + ], + "com.apple.keylayout.Spanish" => &[ + ('!', '¡'), + ('"', '¨'), + ('.', 'ç'), + ('/', '.'), + (':', 'º'), + (';', '´'), + ('<', '¿'), + ('>', 'Ç'), + ('@', '!'), + ('[', 'ñ'), + ('\'', '`'), + ('\\', '\''), + (']', ';'), + ('^', '/'), + ('`', '<'), + ('{', 'Ñ'), + ('|', '"'), + ('}', ':'), + ('~', '>'), + ], + "com.apple.keylayout.Spanish-ISO" => &[ + ('"', '¨'), + ('#', '·'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('.', 'ç'), + ('/', '.'), + (':', 'º'), + (';', '´'), + ('<', '¿'), + ('>', 'Ç'), + ('@', '"'), + ('[', 'ñ'), + ('\'', '`'), + ('\\', '\''), + (']', ';'), + ('^', '&'), + ('`', '<'), + ('{', 'Ñ'), + ('|', '"'), + ('}', '`'), + ('~', '>'), + ], + "com.apple.keylayout.Swedish" => &[ + ('"', '^'), + ('$', '€'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '¨'), + ('\\', '\''), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '*'), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.Swedish-Pro" => &[ + ('"', '^'), + ('$', '€'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '¨'), + ('\\', '\''), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '*'), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.SwedishSami-PC" => &[ + ('"', 'ˆ'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('/', '´'), + (':', 'Å'), + (';', 'å'), + ('<', ';'), + ('=', '`'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '¨'), + ('\\', '@'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'Ö'), + ('|', '*'), + ('}', 'Ä'), + ('~', '>'), + ], + "com.apple.keylayout.SwissFrench" => &[ + ('!', '+'), + ('"', '`'), + ('#', '*'), + ('$', 'ç'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('+', '!'), + ('/', '\''), + (':', 'ü'), + (';', 'è'), + ('<', ';'), + ('=', '¨'), + ('>', ':'), + ('@', '"'), + ('[', 'é'), + ('\'', '^'), + ('\\', '$'), + (']', 'à'), + ('^', '&'), + ('`', '<'), + ('{', 'ö'), + ('|', '£'), + ('}', 'ä'), + ('~', '>'), + ], + "com.apple.keylayout.SwissGerman" => &[ + ('!', '+'), + ('"', '`'), + ('#', '*'), + ('$', 'ç'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('+', '!'), + ('/', '\''), + (':', 'è'), + (';', 'ü'), + ('<', ';'), + ('=', '¨'), + ('>', ':'), + ('@', '"'), + ('[', 'ö'), + ('\'', '^'), + ('\\', '$'), + (']', 'ä'), + ('^', '&'), + ('`', '<'), + ('{', 'é'), + ('|', '£'), + ('}', 'à'), + ('~', '>'), + ], + "com.apple.keylayout.Turkish" => &[ + ('"', '-'), + ('#', '"'), + ('$', '\''), + ('%', '('), + ('&', ')'), + ('(', '%'), + (')', ':'), + ('*', '_'), + (',', 'ö'), + ('-', 'ş'), + ('.', 'ç'), + ('/', '.'), + (':', '$'), + ('<', 'Ö'), + ('>', 'Ç'), + ('@', '*'), + ('[', 'ğ'), + ('\'', ','), + ('\\', 'ü'), + (']', 'ı'), + ('^', '/'), + ('_', 'Ş'), + ('`', '<'), + ('{', 'Ğ'), + ('|', 'Ü'), + ('}', 'I'), + ('~', '>'), + ], + "com.apple.keylayout.Turkish-QWERTY-PC" => &[ + ('"', 'I'), + ('#', '^'), + ('$', '+'), + ('&', '/'), + ('(', ')'), + (')', '='), + ('*', '('), + ('+', ':'), + (',', 'ö'), + ('.', 'ç'), + ('/', '*'), + (':', 'Ş'), + (';', 'ş'), + ('<', 'Ö'), + ('=', '.'), + ('>', 'Ç'), + ('@', '\''), + ('[', 'ğ'), + ('\'', 'ı'), + ('\\', ','), + (']', 'ü'), + ('^', '&'), + ('`', '<'), + ('{', 'Ğ'), + ('|', ';'), + ('}', 'Ü'), + ('~', '>'), + ], + "com.apple.keylayout.Turkish-Standard" => &[ + ('"', 'Ş'), + ('#', '^'), + ('&', '\''), + ('(', ')'), + (')', '='), + ('*', '('), + (',', '.'), + ('.', ','), + (':', 'Ç'), + (';', 'ç'), + ('<', ':'), + ('=', '*'), + ('>', ';'), + ('@', '"'), + ('[', 'ğ'), + ('\'', 'ş'), + ('\\', 'ü'), + (']', 'ı'), + ('^', '&'), + ('`', 'ö'), + ('{', 'Ğ'), + ('|', 'Ü'), + ('}', 'I'), + ('~', 'Ö'), + ], + "com.apple.keylayout.Turkmen" => &[ + ('C', 'Ç'), + ('Q', 'Ä'), + ('V', 'Ý'), + ('X', 'Ü'), + ('[', 'ň'), + ('\\', 'ş'), + (']', 'ö'), + ('^', '№'), + ('`', 'ž'), + ('c', 'ç'), + ('q', 'ä'), + ('v', 'ý'), + ('x', 'ü'), + ('{', 'Ň'), + ('|', 'Ş'), + ('}', 'Ö'), + ('~', 'Ž'), + ], + "com.apple.keylayout.USInternational-PC" => &[('^', 'ˆ'), ('~', '˜')], + "com.apple.keylayout.Welsh" => &[('#', '£')], - Some(HashMap::from_iter(from.chars().zip(to.chars()))) + _ => return None, + }; + + Some(HashMap::from_iter(mappings.into_iter().cloned())) } #[cfg(not(target_os = "macos"))] From 5ff49db92fd3e804f080596433f56fc42b78c887 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 21 Nov 2024 19:57:09 +0200 Subject: [PATCH 097/157] Only show breadcrumbs for terminals when there's a title (#20997) Closes https://github.com/zed-industries/zed/issues/20475 Release Notes: - Fixed terminal title and breadcrumbs behavior --------- Co-authored-by: Thorsten Ball --- Cargo.lock | 1 + assets/settings/default.json | 8 ++++++-- crates/diagnostics/src/diagnostics.rs | 2 +- crates/editor/src/items.rs | 2 +- crates/image_viewer/src/image_viewer.rs | 2 +- crates/search/src/project_search.rs | 2 +- crates/terminal/src/terminal_settings.rs | 10 +++++++--- crates/terminal_view/Cargo.toml | 3 ++- crates/terminal_view/src/terminal_panel.rs | 8 ++++++-- crates/terminal_view/src/terminal_view.rs | 10 +++++----- crates/workspace/src/item.rs | 4 ++-- docs/src/configuring-zed.md | 14 ++++++++++---- 12 files changed, 43 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49c4a10efc..ddf89ba3cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12282,6 +12282,7 @@ name = "terminal_view" version = "0.1.0" dependencies = [ "anyhow", + "breadcrumbs", "client", "collections", "db", diff --git a/assets/settings/default.json b/assets/settings/default.json index d654082e24..819cdcfff6 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -847,8 +847,12 @@ } }, "toolbar": { - // Whether to display the terminal title in its toolbar. - "title": true + // Whether to display the terminal title in its toolbar's breadcrumbs. + // Only shown if the terminal title is not empty. + // + // The shell running in the terminal needs to be configured to emit the title. + // Example: `echo -e "\e]2;New Title\007";` + "breadcrumbs": true } // Set the terminal's font size. If this option is not included, // the terminal will default to matching the buffer's font size. diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 6f20b91689..bd0af230ab 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -776,7 +776,7 @@ impl Item for ProjectDiagnosticsEditor { } } - fn breadcrumb_location(&self) -> ToolbarItemLocation { + fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation { ToolbarItemLocation::PrimaryLeft } diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index d3914f6772..bd54d2c376 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -841,7 +841,7 @@ impl Item for Editor { self.pixel_position_of_newest_cursor } - fn breadcrumb_location(&self) -> ToolbarItemLocation { + fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation { if self.show_breadcrumbs { ToolbarItemLocation::PrimaryLeft } else { diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 5e58cc49fb..1d03e77e76 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -116,7 +116,7 @@ impl Item for ImageView { .map(Icon::from_path) } - fn breadcrumb_location(&self) -> ToolbarItemLocation { + fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation { ToolbarItemLocation::PrimaryLeft } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 1f4492d992..8430fd1f37 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -536,7 +536,7 @@ impl Item for ProjectSearchView { } } - fn breadcrumb_location(&self) -> ToolbarItemLocation { + fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation { if self.has_matches() { ToolbarItemLocation::Secondary } else { diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index e48e23b141..842f00ad9f 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -21,7 +21,7 @@ pub enum TerminalDockPosition { #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct Toolbar { - pub title: bool, + pub breadcrumbs: bool, } #[derive(Debug, Deserialize)] @@ -286,10 +286,14 @@ pub enum WorkingDirectory { // Toolbar related settings #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct ToolbarContent { - /// Whether to display the terminal title in its toolbar. + /// Whether to display the terminal title in breadcrumbs inside the terminal pane. + /// Only shown if the terminal title is not empty. + /// + /// The shell running in the terminal needs to be configured to emit the title. + /// Example: `echo -e "\e]2;New Title\007";` /// /// Default: true - pub title: Option, + pub breadcrumbs: Option, } #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index 64b979cdd6..e57d9d1fc6 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -14,8 +14,9 @@ doctest = false [dependencies] anyhow.workspace = true -db.workspace = true +breadcrumbs.workspace = true collections.workspace = true +db.workspace = true dirs.workspace = true editor.workspace = true futures.workspace = true diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 2ca7561bdb..ee10e924f4 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1,6 +1,7 @@ use std::{ops::ControlFlow, path::PathBuf, sync::Arc}; use crate::{default_working_directory, TerminalView}; +use breadcrumbs::Breadcrumbs; use collections::{HashMap, HashSet}; use db::kvp::KEY_VALUE_STORE; use futures::future::join_all; @@ -138,8 +139,11 @@ impl TerminalPanel { ControlFlow::Break(()) }); let buffer_search_bar = cx.new_view(search::BufferSearchBar::new); - pane.toolbar() - .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx)); + let breadcrumbs = cx.new_view(|_| Breadcrumbs::new()); + pane.toolbar().update(cx, |toolbar, cx| { + toolbar.add_item(buffer_search_bar, cx); + toolbar.add_item(breadcrumbs, cx); + }); pane }); let subscriptions = vec![ diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 21d20599b9..ad0c7f520d 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -109,7 +109,7 @@ pub struct TerminalView { blink_epoch: usize, can_navigate_to_selected_word: bool, workspace_id: Option, - show_title: bool, + show_breadcrumbs: bool, block_below_cursor: Option>, scroll_top: Pixels, _subscriptions: Vec, @@ -189,7 +189,7 @@ impl TerminalView { blink_epoch: 0, can_navigate_to_selected_word: false, workspace_id, - show_title: TerminalSettings::get_global(cx).toolbar.title, + show_breadcrumbs: TerminalSettings::get_global(cx).toolbar.breadcrumbs, block_below_cursor: None, scroll_top: Pixels::ZERO, _subscriptions: vec![ @@ -259,7 +259,7 @@ impl TerminalView { fn settings_changed(&mut self, cx: &mut ViewContext) { let settings = TerminalSettings::get_global(cx); - self.show_title = settings.toolbar.title; + self.show_breadcrumbs = settings.toolbar.breadcrumbs; let new_cursor_shape = settings.cursor_shape.unwrap_or_default(); let old_cursor_shape = self.cursor_shape; @@ -1145,8 +1145,8 @@ impl Item for TerminalView { Some(Box::new(handle.clone())) } - fn breadcrumb_location(&self) -> ToolbarItemLocation { - if self.show_title { + fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation { + if self.show_breadcrumbs && !self.terminal().read(cx).breadcrumb_text.trim().is_empty() { ToolbarItemLocation::PrimaryLeft } else { ToolbarItemLocation::Hidden diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 5f14b9ba62..a7bf90dd17 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -278,7 +278,7 @@ pub trait Item: FocusableView + EventEmitter { None } - fn breadcrumb_location(&self) -> ToolbarItemLocation { + fn breadcrumb_location(&self, _: &AppContext) -> ToolbarItemLocation { ToolbarItemLocation::Hidden } @@ -827,7 +827,7 @@ impl ItemHandle for View { } fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation { - self.read(cx).breadcrumb_location() + self.read(cx).breadcrumb_location(cx) } fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option> { diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index b4da7901a1..4991ff1119 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -1628,7 +1628,7 @@ List of `integer` column numbers "button": false, "shell": {}, "toolbar": { - "title": true + "breadcrumbs": true }, "working_directory": "current_project_directory" } @@ -1946,7 +1946,7 @@ Disable with: ## Terminal: Toolbar -- Description: Whether or not to show various elements in the terminal toolbar. It only affects terminals placed in the editor pane. +- Description: Whether or not to show various elements in the terminal toolbar. - Setting: `toolbar` - Default: @@ -1954,7 +1954,7 @@ Disable with: { "terminal": { "toolbar": { - "title": true + "breadcrumbs": true } } } @@ -1962,7 +1962,13 @@ Disable with: **Options** -At the moment, only the `title` option is available, it controls displaying of the terminal title that can be changed via `PROMPT_COMMAND`. If the title is hidden, the terminal toolbar is not displayed. +At the moment, only the `breadcrumbs` option is available, it controls displaying of the terminal title that can be changed via `PROMPT_COMMAND`. + +If the terminal title is empty, the breadcrumbs won't be shown. + +The shell running in the terminal needs to be configured to emit the title. + +Example command to set the title: `echo -e "\e]2;New Title\007";` ### Terminal: Button From 571c7d4f6645528c0bf1d2bcacfd623676c69ee7 Mon Sep 17 00:00:00 2001 From: Nils Koch Date: Thu, 21 Nov 2024 18:03:40 +0000 Subject: [PATCH 098/157] Improve project_panel diagnostic icon knockout colors (#20760) Closes #20572 Release Notes: - N/A cc @danilo-leal @WeetHet --- crates/project_panel/src/project_panel.rs | 46 +++++++++++++---------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 9432d1e6d5..5ad2c2d12e 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -101,6 +101,7 @@ pub struct ProjectPanel { // We keep track of the mouse down state on entries so we don't flash the UI // in case a user clicks to open a file. mouse_down: bool, + hovered_entries: HashSet, } #[derive(Clone, Debug)] @@ -139,6 +140,7 @@ struct EntryDetails { is_marked: bool, is_editing: bool, is_processing: bool, + is_hovered: bool, is_cut: bool, filename_text_color: Color, diagnostic_severity: Option, @@ -256,7 +258,7 @@ fn get_item_color(cx: &ViewContext) -> ItemColors { ItemColors { default: colors.surface_background, - hover: colors.element_active, + hover: colors.ghost_element_hover, drag_over: colors.drop_target_background, marked_active: colors.ghost_element_selected, } @@ -380,6 +382,7 @@ impl ProjectPanel { diagnostics: Default::default(), scroll_handle, mouse_down: false, + hovered_entries: Default::default(), }; this.update_visible_entries(None, cx); @@ -2465,6 +2468,7 @@ impl ProjectPanel { is_expanded, is_selected: self.selection == Some(selection), is_marked, + is_hovered: self.hovered_entries.contains(&entry.id), is_editing: false, is_processing: false, is_cut: self @@ -2594,6 +2598,7 @@ impl ProjectPanel { let is_active = self .selection .map_or(false, |selection| selection.entry_id == entry_id); + let is_hovered = details.is_hovered; let width = self.size(cx); let file_name = details.filename.clone(); @@ -2626,6 +2631,14 @@ impl ProjectPanel { marked_selections: selections, }; + let (bg_color, border_color) = match (is_hovered, is_marked || is_active, self.mouse_down) { + (true, _, true) => (item_colors.marked_active, item_colors.hover), + (true, false, false) => (item_colors.hover, item_colors.hover), + (true, true, false) => (item_colors.hover, item_colors.marked_active), + (false, true, _) => (item_colors.marked_active, item_colors.marked_active), + _ => (item_colors.default, item_colors.default), + }; + div() .id(entry_id.to_proto() as usize) .when(is_local, |div| { @@ -2703,6 +2716,14 @@ impl ProjectPanel { cx.propagate(); }), ) + .on_hover(cx.listener(move |this, hover, cx| { + if *hover { + this.hovered_entries.insert(entry_id); + } else { + this.hovered_entries.remove(&entry_id); + } + cx.notify(); + })) .on_click(cx.listener(move |this, event: &gpui::ClickEvent, cx| { if event.down.button == MouseButton::Right || event.down.first_mouse || show_editor { @@ -2763,11 +2784,13 @@ impl ProjectPanel { } })) .cursor_pointer() + .bg(bg_color) + .border_color(border_color) .child( ListItem::new(entry_id.to_proto() as usize) .indent_level(depth) .indent_step_size(px(settings.indent_size)) - .selected(is_marked || is_active) + .selectable(false) .when_some(canonical_path, |this, path| { this.end_slot::( div() @@ -2807,11 +2830,7 @@ impl ProjectPanel { } else { IconDecorationKind::Dot }, - if is_marked || is_active { - item_colors.marked_active - } else { - item_colors.default - }, + bg_color, cx, ) .color(decoration_color.color(cx)) @@ -2924,19 +2943,6 @@ impl ProjectPanel { .border_1() .border_r_2() .rounded_none() - .hover(|style| { - if is_active { - style - } else { - style.bg(item_colors.hover).border_color(item_colors.hover) - } - }) - .when(is_marked || is_active, |this| { - this.when(is_marked, |this| { - this.bg(item_colors.marked_active) - .border_color(item_colors.marked_active) - }) - }) .when( !self.mouse_down && is_active && self.focus_handle.contains_focused(cx), |this| this.border_color(Color::Selected.color(cx)), From 268ac4c0476f56453639aba36715f8042e542815 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Thu, 21 Nov 2024 18:10:25 +0000 Subject: [PATCH 099/157] Implement readline/emacs/macos style ctrl-k cut and ctrl-y yank (#21003) - Added support for ctrl-k / ctrl-y alternate cut/yank buffer on macos. Co-authored-by: Conrad Irwin --- assets/keymaps/default-macos.json | 3 ++- crates/editor/src/actions.rs | 2 ++ crates/editor/src/editor.rs | 42 ++++++++++++++++++++++++++----- crates/editor/src/element.rs | 2 ++ 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 82edba3305..5b416db9b2 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -49,8 +49,9 @@ "ctrl-d": "editor::Delete", "tab": "editor::Tab", "shift-tab": "editor::TabPrev", - "ctrl-k": "editor::CutToEndOfLine", "ctrl-t": "editor::Transpose", + "ctrl-k": "editor::KillRingCut", + "ctrl-y": "editor::KillRingYank", "cmd-k q": "editor::Rewrap", "cmd-k cmd-q": "editor::Rewrap", "cmd-backspace": "editor::DeleteToBeginningOfLine", diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index dcfc291968..5b11b18bc2 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -271,6 +271,8 @@ gpui::actions!( Hover, Indent, JoinLines, + KillRingCut, + KillRingYank, LineDown, LineUp, MoveDown, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cc450c573f..b31938bcfd 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -74,7 +74,7 @@ use gpui::{ div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement, AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry, ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent, - FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext, + FocusableView, FontId, FontWeight, Global, HighlightStyle, Hsla, InteractiveText, KeyContext, ListSizingBehavior, Model, ModelContext, MouseButton, PaintQuad, ParentElement, Pixels, Render, ScrollStrategy, SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, View, @@ -7364,7 +7364,7 @@ impl Editor { .update(cx, |buffer, cx| buffer.edit(edits, None, cx)); } - pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext) { + pub fn cut_common(&mut self, cx: &mut ViewContext) -> ClipboardItem { let mut text = String::new(); let buffer = self.buffer.read(cx).snapshot(cx); let mut selections = self.selections.all::(cx); @@ -7408,11 +7408,38 @@ impl Editor { s.select(selections); }); this.insert("", cx); - cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata( - text, - clipboard_selections, - )); }); + ClipboardItem::new_string_with_json_metadata(text, clipboard_selections) + } + + pub fn cut(&mut self, _: &Cut, cx: &mut ViewContext) { + let item = self.cut_common(cx); + cx.write_to_clipboard(item); + } + + pub fn kill_ring_cut(&mut self, _: &KillRingCut, cx: &mut ViewContext) { + self.change_selections(None, cx, |s| { + s.move_with(|snapshot, sel| { + if sel.is_empty() { + sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row())) + } + }); + }); + let item = self.cut_common(cx); + cx.set_global(KillRing(item)) + } + + pub fn kill_ring_yank(&mut self, _: &KillRingYank, cx: &mut ViewContext) { + let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() { + if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() { + (kill_ring.text().to_string(), kill_ring.metadata_json()) + } else { + return; + } + } else { + return; + }; + self.do_paste(&text, metadata, false, cx); } pub fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { @@ -15145,4 +15172,7 @@ fn check_multiline_range(buffer: &Buffer, range: Range) -> Range { } } +pub struct KillRing(ClipboardItem); +impl Global for KillRing {} + const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6e4538ae6d..0c403022a3 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -217,6 +217,8 @@ impl EditorElement { register_action(view, cx, Editor::transpose); register_action(view, cx, Editor::rewrap); register_action(view, cx, Editor::cut); + register_action(view, cx, Editor::kill_ring_cut); + register_action(view, cx, Editor::kill_ring_yank); register_action(view, cx, Editor::copy); register_action(view, cx, Editor::paste); register_action(view, cx, Editor::undo); From c16dfc1a39f481c9fb7db0b158522fcebbe142fc Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Nov 2024 13:37:34 -0500 Subject: [PATCH 100/157] title_bar: Remove dependency on `command_palette` (#21006) This PR removes the `title_bar` crate's dependency on the `command_palette`. The `command_palette::Toggle` action now resides at `zed_actions::command_palette::Toggle`. Release Notes: - N/A --- Cargo.lock | 1 - crates/command_palette/src/command_palette.rs | 6 ++---- crates/title_bar/Cargo.toml | 1 - crates/title_bar/src/application_menu.rs | 5 ++++- crates/zed/src/zed/app_menus.rs | 2 +- crates/zed_actions/src/lib.rs | 6 ++++++ 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ddf89ba3cd..f416381225 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12607,7 +12607,6 @@ dependencies = [ "call", "client", "collections", - "command_palette", "editor", "feature_flags", "feedback", diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 21dd06e81c..11bc6848fe 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -11,7 +11,7 @@ use command_palette_hooks::{ }; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - actions, Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Global, + Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Global, ParentElement, Render, Styled, Task, UpdateGlobal, View, ViewContext, VisualContext, WeakView, }; use picker::{Picker, PickerDelegate}; @@ -21,9 +21,7 @@ use settings::Settings; use ui::{h_flex, prelude::*, v_flex, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ModalView, Workspace, WorkspaceSettings}; -use zed_actions::OpenZedUrl; - -actions!(command_palette, [Toggle]); +use zed_actions::{command_palette::Toggle, OpenZedUrl}; pub fn init(cx: &mut AppContext) { client::init_settings(cx); diff --git a/crates/title_bar/Cargo.toml b/crates/title_bar/Cargo.toml index 05bd1be502..809915b4dc 100644 --- a/crates/title_bar/Cargo.toml +++ b/crates/title_bar/Cargo.toml @@ -31,7 +31,6 @@ test-support = [ auto_update.workspace = true call.workspace = true client.workspace = true -command_palette.workspace = true feedback.workspace = true feature_flags.workspace = true gpui.workspace = true diff --git a/crates/title_bar/src/application_menu.rs b/crates/title_bar/src/application_menu.rs index c3994f81d7..3d5a774e8f 100644 --- a/crates/title_bar/src/application_menu.rs +++ b/crates/title_bar/src/application_menu.rs @@ -18,7 +18,10 @@ impl Render for ApplicationMenu { .menu(move |cx| { ContextMenu::build(cx, move |menu, cx| { menu.header("Workspace") - .action("Open Command Palette", Box::new(command_palette::Toggle)) + .action( + "Open Command Palette", + Box::new(zed_actions::command_palette::Toggle), + ) .when_some(cx.focused(), |menu, focused| menu.context(focused)) .custom_row(move |cx| { h_flex() diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index 824704fca5..4a2f351627 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -146,7 +146,7 @@ pub fn app_menus() -> Vec { MenuItem::action("Back", workspace::GoBack), MenuItem::action("Forward", workspace::GoForward), MenuItem::separator(), - MenuItem::action("Command Palette...", command_palette::Toggle), + MenuItem::action("Command Palette...", zed_actions::command_palette::Toggle), MenuItem::separator(), MenuItem::action("Go to File...", workspace::ToggleFileFinder::default()), // MenuItem::action("Go to Symbol in Project", project_symbols::Toggle), diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 53f5b202a8..b777f03646 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -44,6 +44,12 @@ actions!( ] ); +pub mod command_palette { + use gpui::actions; + + actions!(command_palette, [Toggle]); +} + #[derive(Clone, Default, Deserialize, PartialEq)] pub struct InlineAssist { pub prompt: Option, From 02447a8552a7c48e3dc5fbb54e15ee400e41ab2a Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 21 Nov 2024 11:55:22 -0700 Subject: [PATCH 101/157] Use our own git clone in draft release notes (#20956) It turns out that messing with the git repo created by the github action is tricky, so we'll just clone our own. On my machine, a shallow tree-less clone takes <500ms Release Notes: - N/A --- script/create-draft-release | 2 +- script/draft-release-notes | 70 ++++++++++++++++++++++--------------- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/script/create-draft-release b/script/create-draft-release index e72c6d141c..95b1a1450a 100755 --- a/script/create-draft-release +++ b/script/create-draft-release @@ -5,4 +5,4 @@ if [[ "$GITHUB_REF_NAME" == *"-pre" ]]; then preview="-p" fi -gh release create -d "$GITHUB_REF_NAME" -F "$1" $preview +gh release create -t "$GITHUB_REF_NAME" -d "$GITHUB_REF_NAME" -F "$1" $preview diff --git a/script/draft-release-notes b/script/draft-release-notes index eeb53bbb22..1ef276718d 100755 --- a/script/draft-release-notes +++ b/script/draft-release-notes @@ -19,24 +19,45 @@ async function main() { process.exit(1); } - let priorVersion = [parts[0], parts[1], parts[2] - 1].join("."); - let suffix = ""; - - if (channel == "preview") { - suffix = "-pre"; - if (parts[2] == 0) { - priorVersion = [parts[0], parts[1] - 1, 0].join("."); - } - } else if (!ensureTag(`v${priorVersion}`)) { - console.log("Copy the release notes from preview."); + // currently we can only draft notes for patch releases. + if (parts[2] == 0) { process.exit(0); } + let priorVersion = [parts[0], parts[1], parts[2] - 1].join("."); + let suffix = channel == "preview" ? "-pre" : ""; let [tag, priorTag] = [`v${version}${suffix}`, `v${priorVersion}${suffix}`]; - if (!ensureTag(tag) || !ensureTag(priorTag)) { - console.log("Could not draft release notes, missing a tag:", tag, priorTag); - process.exit(0); + try { + execFileSync("rm", ["-rf", "target/shallow_clone"]); + execFileSync("git", [ + "clone", + "https://github.com/zed-industries/zed", + "target/shallow_clone", + "--filter=tree:0", + "--no-checkout", + "--branch", + tag, + "--depth", + 100, + ]); + execFileSync("git", [ + "-C", + "target/shallow_clone", + "rev-parse", + "--verify", + tag, + ]); + execFileSync("git", [ + "-C", + "target/shallow_clone", + "rev-parse", + "--verify", + priorTag, + ]); + } catch (e) { + console.error(e.stderr.toString()); + process.exit(1); } const newCommits = getCommits(priorTag, tag); @@ -69,7 +90,13 @@ async function main() { function getCommits(oldTag, newTag) { const pullRequestNumbers = execFileSync( "git", - ["log", `${oldTag}..${newTag}`, "--format=DIVIDER\n%H|||%B"], + [ + "-C", + "target/shallow_clone", + "log", + `${oldTag}..${newTag}`, + "--format=DIVIDER\n%H|||%B", + ], { encoding: "utf8" }, ) .replace(/\r\n/g, "\n") @@ -99,18 +126,3 @@ function getCommits(oldTag, newTag) { return pullRequestNumbers; } - -function ensureTag(tag) { - try { - execFileSync("git", ["rev-parse", "--verify", tag]); - return true; - } catch (e) { - try { - execFileSync("git"[("fetch", "origin", "--shallow-exclude", tag)]); - execFileSync("git"[("fetch", "origin", "--deepen", "1")]); - return true; - } catch (e) { - return false; - } - } -} From 841d3221b34fa5786d85fb7c2cd7e9cc3ae51c15 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 21 Nov 2024 11:59:02 -0700 Subject: [PATCH 102/157] Auto release preview patch releases (#20886) This should make the process of releasing patch releases to preview less toilful Release Notes: - N/A --- .github/workflows/ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 43af9309fe..8f2f08aa1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -404,3 +404,16 @@ jobs: target/release/zed-linux-aarch64.tar.gz env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + auto-release-preview: + name: Auto release preview + if: ${{ startsWith(github.ref, 'refs/tags/v') && endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre') }} + needs: [bundle-mac, bundle-linux, bundle-linux-aarch64] + runs-on: + - self-hosted + - bundle + steps: + - name: gh release + run: gh release edit $GITHUB_REF_NAME --draft=false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From f62ccf9c8a68a70353460d23586c557e89f7ec9b Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Nov 2024 14:11:57 -0500 Subject: [PATCH 103/157] Extract `auto_update_ui` crate (#21008) This PR extracts an `auto_update_ui` crate out of the `auto_update` crate. This allows `auto_update` to not depend on heavier crates like `editor`, which in turn allows other downstream crates to start building sooner. Release Notes: - N/A --- Cargo.lock | 26 ++- Cargo.toml | 2 + crates/auto_update/Cargo.toml | 5 - crates/auto_update/src/auto_update.rs | 170 ++---------------- crates/auto_update_ui/Cargo.toml | 28 +++ crates/auto_update_ui/LICENSE-GPL | 1 + crates/auto_update_ui/src/auto_update_ui.rs | 147 +++++++++++++++ .../src/update_notification.rs | 0 crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 1 + crates/zed/src/zed.rs | 2 +- 11 files changed, 217 insertions(+), 166 deletions(-) create mode 100644 crates/auto_update_ui/Cargo.toml create mode 120000 crates/auto_update_ui/LICENSE-GPL create mode 100644 crates/auto_update_ui/src/auto_update_ui.rs rename crates/{auto_update => auto_update_ui}/src/update_notification.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index f416381225..8888754a33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1014,26 +1014,41 @@ dependencies = [ "anyhow", "client", "db", - "editor", "gpui", "http_client", "log", - "markdown_preview", - "menu", "paths", "release_channel", "schemars", "serde", - "serde_derive", "serde_json", "settings", "smol", "tempfile", - "util", "which 6.0.3", "workspace", ] +[[package]] +name = "auto_update_ui" +version = "0.1.0" +dependencies = [ + "anyhow", + "auto_update", + "client", + "editor", + "gpui", + "http_client", + "markdown_preview", + "menu", + "release_channel", + "serde", + "serde_json", + "smol", + "util", + "workspace", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -15464,6 +15479,7 @@ dependencies = [ "async-watch", "audio", "auto_update", + "auto_update_ui", "backtrace", "breadcrumbs", "call", diff --git a/Cargo.toml b/Cargo.toml index a5555864d1..b1feec52ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "crates/assistant_tool", "crates/audio", "crates/auto_update", + "crates/auto_update_ui", "crates/breadcrumbs", "crates/call", "crates/channel", @@ -187,6 +188,7 @@ assistant_slash_command = { path = "crates/assistant_slash_command" } assistant_tool = { path = "crates/assistant_tool" } audio = { path = "crates/audio" } auto_update = { path = "crates/auto_update" } +auto_update_ui = { path = "crates/auto_update_ui" } breadcrumbs = { path = "crates/breadcrumbs" } call = { path = "crates/call" } channel = { path = "crates/channel" } diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml index d47a9f9ae0..fa46b04a78 100644 --- a/crates/auto_update/Cargo.toml +++ b/crates/auto_update/Cargo.toml @@ -16,21 +16,16 @@ doctest = false anyhow.workspace = true client.workspace = true db.workspace = true -editor.workspace = true gpui.workspace = true http_client.workspace = true log.workspace = true -markdown_preview.workspace = true -menu.workspace = true paths.workspace = true release_channel.workspace = true schemars.workspace = true serde.workspace = true -serde_derive.workspace = true serde_json.workspace = true settings.workspace = true smol.workspace = true tempfile.workspace = true -util.workspace = true which.workspace = true workspace.workspace = true diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 6d95daecb7..0f9999b918 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -1,27 +1,19 @@ -mod update_notification; - use anyhow::{anyhow, Context, Result}; use client::{Client, TelemetrySettings}; use db::kvp::KEY_VALUE_STORE; use db::RELEASE_CHANNEL; -use editor::{Editor, MultiBuffer}; use gpui::{ actions, AppContext, AsyncAppContext, Context as _, Global, Model, ModelContext, - SemanticVersion, SharedString, Task, View, ViewContext, VisualContext, WindowContext, + SemanticVersion, Task, WindowContext, }; - -use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView}; -use paths::remote_servers_dir; -use schemars::JsonSchema; -use serde::Deserialize; -use serde_derive::Serialize; -use smol::{fs, io::AsyncReadExt}; - -use settings::{Settings, SettingsSources, SettingsStore}; -use smol::{fs::File, process::Command}; - use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; -use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; +use paths::remote_servers_dir; +use release_channel::{AppCommitSha, ReleaseChannel}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use settings::{Settings, SettingsSources, SettingsStore}; +use smol::{fs, io::AsyncReadExt}; +use smol::{fs::File, process::Command}; use std::{ env::{ self, @@ -32,24 +24,13 @@ use std::{ sync::Arc, time::Duration, }; -use update_notification::UpdateNotification; -use util::ResultExt; use which::which; -use workspace::notifications::NotificationId; use workspace::Workspace; const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification"; const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60); -actions!( - auto_update, - [ - Check, - DismissErrorMessage, - ViewReleaseNotes, - ViewReleaseNotesLocally - ] -); +actions!(auto_update, [Check, DismissErrorMessage, ViewReleaseNotes,]); #[derive(Serialize)] struct UpdateRequestBody { @@ -146,12 +127,6 @@ struct GlobalAutoUpdate(Option>); impl Global for GlobalAutoUpdate {} -#[derive(Deserialize)] -struct ReleaseNotesBody { - title: String, - release_notes: String, -} - pub fn init(http_client: Arc, cx: &mut AppContext) { AutoUpdateSetting::register(cx); @@ -161,10 +136,6 @@ pub fn init(http_client: Arc, cx: &mut AppContext) { workspace.register_action(|_, action, cx| { view_release_notes(action, cx); }); - - workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| { - view_release_notes_locally(workspace, cx); - }); }) .detach(); @@ -264,121 +235,6 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<( None } -fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext) { - let release_channel = ReleaseChannel::global(cx); - - let url = match release_channel { - ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"), - ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"), - _ => None, - }; - - if let Some(url) = url { - cx.open_url(url); - return; - } - - let version = AppVersion::global(cx).to_string(); - - let client = client::Client::global(cx).http_client(); - let url = client.build_url(&format!( - "/api/release_notes/v2/{}/{}", - release_channel.dev_name(), - version - )); - - let markdown = workspace - .app_state() - .languages - .language_for_name("Markdown"); - - workspace - .with_local_workspace(cx, move |_, cx| { - cx.spawn(|workspace, mut cx| async move { - let markdown = markdown.await.log_err(); - let response = client.get(&url, Default::default(), true).await; - let Some(mut response) = response.log_err() else { - return; - }; - - let mut body = Vec::new(); - response.body_mut().read_to_end(&mut body).await.ok(); - - let body: serde_json::Result = - serde_json::from_slice(body.as_slice()); - - if let Ok(body) = body { - workspace - .update(&mut cx, |workspace, cx| { - let project = workspace.project().clone(); - let buffer = project.update(cx, |project, cx| { - project.create_local_buffer("", markdown, cx) - }); - buffer.update(cx, |buffer, cx| { - buffer.edit([(0..0, body.release_notes)], None, cx) - }); - let language_registry = project.read(cx).languages().clone(); - - let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); - - let tab_description = SharedString::from(body.title.to_string()); - let editor = cx.new_view(|cx| { - Editor::for_multibuffer(buffer, Some(project), true, cx) - }); - let workspace_handle = workspace.weak_handle(); - let view: View = MarkdownPreviewView::new( - MarkdownPreviewMode::Default, - editor, - workspace_handle, - language_registry, - Some(tab_description), - cx, - ); - workspace.add_item_to_active_pane( - Box::new(view.clone()), - None, - true, - cx, - ); - cx.notify(); - }) - .log_err(); - } - }) - .detach(); - }) - .detach(); -} - -pub fn notify_of_any_new_update(cx: &mut ViewContext) -> Option<()> { - let updater = AutoUpdater::get(cx)?; - let version = updater.read(cx).current_version; - let should_show_notification = updater.read(cx).should_show_update_notification(cx); - - cx.spawn(|workspace, mut cx| async move { - let should_show_notification = should_show_notification.await?; - if should_show_notification { - workspace.update(&mut cx, |workspace, cx| { - let workspace_handle = workspace.weak_handle(); - workspace.show_notification( - NotificationId::unique::(), - cx, - |cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)), - ); - updater.update(cx, |updater, cx| { - updater - .set_should_show_update_notification(false, cx) - .detach_and_log_err(cx); - }); - })?; - } - anyhow::Ok(()) - }) - .detach(); - - None -} - impl AutoUpdater { pub fn get(cx: &mut AppContext) -> Option> { cx.default_global::().0.clone() @@ -423,6 +279,10 @@ impl AutoUpdater { })); } + pub fn current_version(&self) -> SemanticVersion { + self.current_version + } + pub fn status(&self) -> AutoUpdateStatus { self.status.clone() } @@ -646,7 +506,7 @@ impl AutoUpdater { Ok(()) } - fn set_should_show_update_notification( + pub fn set_should_show_update_notification( &self, should_show: bool, cx: &AppContext, @@ -668,7 +528,7 @@ impl AutoUpdater { }) } - fn should_show_update_notification(&self, cx: &AppContext) -> Task> { + pub fn should_show_update_notification(&self, cx: &AppContext) -> Task> { cx.background_executor().spawn(async move { Ok(KEY_VALUE_STORE .read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)? diff --git a/crates/auto_update_ui/Cargo.toml b/crates/auto_update_ui/Cargo.toml new file mode 100644 index 0000000000..1d62d295b7 --- /dev/null +++ b/crates/auto_update_ui/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "auto_update_ui" +version = "0.1.0" +edition = "2021" +publish = false +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/auto_update_ui.rs" + +[dependencies] +anyhow.workspace = true +auto_update.workspace = true +client.workspace = true +editor.workspace = true +gpui.workspace = true +http_client.workspace = true +markdown_preview.workspace = true +menu.workspace = true +release_channel.workspace = true +serde.workspace = true +serde_json.workspace = true +smol.workspace = true +util.workspace = true +workspace.workspace = true diff --git a/crates/auto_update_ui/LICENSE-GPL b/crates/auto_update_ui/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/auto_update_ui/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/auto_update_ui/src/auto_update_ui.rs b/crates/auto_update_ui/src/auto_update_ui.rs new file mode 100644 index 0000000000..9114375e88 --- /dev/null +++ b/crates/auto_update_ui/src/auto_update_ui.rs @@ -0,0 +1,147 @@ +mod update_notification; + +use auto_update::AutoUpdater; +use editor::{Editor, MultiBuffer}; +use gpui::{actions, prelude::*, AppContext, SharedString, View, ViewContext}; +use http_client::HttpClient; +use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPreviewView}; +use release_channel::{AppVersion, ReleaseChannel}; +use serde::Deserialize; +use smol::io::AsyncReadExt; +use util::ResultExt as _; +use workspace::notifications::NotificationId; +use workspace::Workspace; + +use crate::update_notification::UpdateNotification; + +actions!(auto_update, [ViewReleaseNotesLocally]); + +pub fn init(cx: &mut AppContext) { + cx.observe_new_views(|workspace: &mut Workspace, _cx| { + workspace.register_action(|workspace, _: &ViewReleaseNotesLocally, cx| { + view_release_notes_locally(workspace, cx); + }); + }) + .detach(); +} + +#[derive(Deserialize)] +struct ReleaseNotesBody { + title: String, + release_notes: String, +} + +fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext) { + let release_channel = ReleaseChannel::global(cx); + + let url = match release_channel { + ReleaseChannel::Nightly => Some("https://github.com/zed-industries/zed/commits/nightly/"), + ReleaseChannel::Dev => Some("https://github.com/zed-industries/zed/commits/main/"), + _ => None, + }; + + if let Some(url) = url { + cx.open_url(url); + return; + } + + let version = AppVersion::global(cx).to_string(); + + let client = client::Client::global(cx).http_client(); + let url = client.build_url(&format!( + "/api/release_notes/v2/{}/{}", + release_channel.dev_name(), + version + )); + + let markdown = workspace + .app_state() + .languages + .language_for_name("Markdown"); + + workspace + .with_local_workspace(cx, move |_, cx| { + cx.spawn(|workspace, mut cx| async move { + let markdown = markdown.await.log_err(); + let response = client.get(&url, Default::default(), true).await; + let Some(mut response) = response.log_err() else { + return; + }; + + let mut body = Vec::new(); + response.body_mut().read_to_end(&mut body).await.ok(); + + let body: serde_json::Result = + serde_json::from_slice(body.as_slice()); + + if let Ok(body) = body { + workspace + .update(&mut cx, |workspace, cx| { + let project = workspace.project().clone(); + let buffer = project.update(cx, |project, cx| { + project.create_local_buffer("", markdown, cx) + }); + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, body.release_notes)], None, cx) + }); + let language_registry = project.read(cx).languages().clone(); + + let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); + + let tab_description = SharedString::from(body.title.to_string()); + let editor = cx.new_view(|cx| { + Editor::for_multibuffer(buffer, Some(project), true, cx) + }); + let workspace_handle = workspace.weak_handle(); + let view: View = MarkdownPreviewView::new( + MarkdownPreviewMode::Default, + editor, + workspace_handle, + language_registry, + Some(tab_description), + cx, + ); + workspace.add_item_to_active_pane( + Box::new(view.clone()), + None, + true, + cx, + ); + cx.notify(); + }) + .log_err(); + } + }) + .detach(); + }) + .detach(); +} + +pub fn notify_of_any_new_update(cx: &mut ViewContext) -> Option<()> { + let updater = AutoUpdater::get(cx)?; + let version = updater.read(cx).current_version(); + let should_show_notification = updater.read(cx).should_show_update_notification(cx); + + cx.spawn(|workspace, mut cx| async move { + let should_show_notification = should_show_notification.await?; + if should_show_notification { + workspace.update(&mut cx, |workspace, cx| { + let workspace_handle = workspace.weak_handle(); + workspace.show_notification( + NotificationId::unique::(), + cx, + |cx| cx.new_view(|_| UpdateNotification::new(version, workspace_handle)), + ); + updater.update(cx, |updater, cx| { + updater + .set_should_show_update_notification(false, cx) + .detach_and_log_err(cx); + }); + })?; + } + anyhow::Ok(()) + }) + .detach(); + + None +} diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update_ui/src/update_notification.rs similarity index 100% rename from crates/auto_update/src/update_notification.rs rename to crates/auto_update_ui/src/update_notification.rs diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 8d12d7b9f9..b55ebce2b9 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -23,6 +23,7 @@ assistant.workspace = true async-watch.workspace = true audio.workspace = true auto_update.workspace = true +auto_update_ui.workspace = true backtrace = "0.3" breadcrumbs.workspace = true call.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 9dbe00c617..f7aabb2626 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -367,6 +367,7 @@ fn main() { AppState::set_global(Arc::downgrade(&app_state), cx); auto_update::init(client.http_client(), cx); + auto_update_ui::init(cx); reliability::init( client.http_client(), system_id.as_ref().map(|id| id.to_string()), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 73ecd00192..909afc207d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -223,7 +223,7 @@ pub fn initialize_workspace( status_bar.add_right_item(cursor_position, cx); }); - auto_update::notify_of_any_new_update(cx); + auto_update_ui::notify_of_any_new_update(cx); let handle = cx.view().downgrade(); cx.on_window_should_close(move |cx| { From 6b2f1cc54341163f9f441d39ff067eeb50c0bd13 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Nov 2024 14:33:58 -0500 Subject: [PATCH 104/157] title_bar: Remove dependency on `theme_selector` (#21009) This PR removes the `title_bar` crate's dependency on the `theme_selector`. The `theme_selector::Toggle` action now resides at `zed_actions::theme_selector::Toggle`. Release Notes: - N/A --- Cargo.lock | 2 +- crates/extensions_ui/src/extensions_ui.rs | 2 +- crates/theme_selector/Cargo.toml | 1 + crates/theme_selector/src/theme_selector.rs | 13 +++---------- crates/title_bar/Cargo.toml | 1 - crates/title_bar/src/title_bar.rs | 10 ++++++++-- crates/zed/src/zed/app_menus.rs | 5 ++++- crates/zed_actions/src/lib.rs | 13 +++++++++++++ 8 files changed, 31 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8888754a33..2bb4c4e0c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12410,6 +12410,7 @@ dependencies = [ "ui", "util", "workspace", + "zed_actions", ] [[package]] @@ -12637,7 +12638,6 @@ dependencies = [ "smallvec", "story", "theme", - "theme_selector", "tree-sitter-md", "ui", "util", diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 01e2b1dd66..1586f3546e 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -257,7 +257,7 @@ impl ExtensionsPage { .update(cx, |workspace, cx| { theme_selector::toggle( workspace, - &theme_selector::Toggle { + &zed_actions::theme_selector::Toggle { themes_filter: Some(themes), }, cx, diff --git a/crates/theme_selector/Cargo.toml b/crates/theme_selector/Cargo.toml index ec7e9aa877..dc0d5f3ac4 100644 --- a/crates/theme_selector/Cargo.toml +++ b/crates/theme_selector/Cargo.toml @@ -25,5 +25,6 @@ theme.workspace = true ui.workspace = true util.workspace = true workspace.workspace = true +zed_actions.workspace = true [dev-dependencies] diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index d0763c2793..e09ad40bf4 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -2,25 +2,18 @@ use client::telemetry::Telemetry; use fs::Fs; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; use gpui::{ - actions, impl_actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, - UpdateGlobal, View, ViewContext, VisualContext, WeakView, + actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, UpdateGlobal, View, + ViewContext, VisualContext, WeakView, }; use picker::{Picker, PickerDelegate}; -use serde::Deserialize; use settings::{update_settings_file, SettingsStore}; use std::sync::Arc; use theme::{Appearance, Theme, ThemeMeta, ThemeRegistry, ThemeSettings}; use ui::{prelude::*, v_flex, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ui::HighlightedLabel, ModalView, Workspace}; +use zed_actions::theme_selector::Toggle; -#[derive(PartialEq, Clone, Default, Debug, Deserialize)] -pub struct Toggle { - /// A list of theme names to filter the theme selector down to. - pub themes_filter: Option>, -} - -impl_actions!(theme_selector, [Toggle]); actions!(theme_selector, [Reload]); pub fn init(cx: &mut AppContext) { diff --git a/crates/title_bar/Cargo.toml b/crates/title_bar/Cargo.toml index 809915b4dc..75cb49b5a8 100644 --- a/crates/title_bar/Cargo.toml +++ b/crates/title_bar/Cargo.toml @@ -42,7 +42,6 @@ serde.workspace = true smallvec.workspace = true story = { workspace = true, optional = true } theme.workspace = true -theme_selector.workspace = true ui.workspace = true util.workspace = true vcs_menu.workspace = true diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index bcf13a5ac7..744f4ce26d 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -579,7 +579,10 @@ impl TitleBar { }) .action("Settings", zed_actions::OpenSettings.boxed_clone()) .action("Key Bindings", Box::new(zed_actions::OpenKeymap)) - .action("Themes…", theme_selector::Toggle::default().boxed_clone()) + .action( + "Themes…", + zed_actions::theme_selector::Toggle::default().boxed_clone(), + ) .action("Extensions", zed_actions::Extensions.boxed_clone()) .separator() .link( @@ -615,7 +618,10 @@ impl TitleBar { ContextMenu::build(cx, |menu, _| { menu.action("Settings", zed_actions::OpenSettings.boxed_clone()) .action("Key Bindings", Box::new(zed_actions::OpenKeymap)) - .action("Themes…", theme_selector::Toggle::default().boxed_clone()) + .action( + "Themes…", + zed_actions::theme_selector::Toggle::default().boxed_clone(), + ) .action("Extensions", zed_actions::Extensions.boxed_clone()) .separator() .link( diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index 4a2f351627..3affa31986 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -23,7 +23,10 @@ pub fn app_menus() -> Vec { zed_actions::OpenDefaultKeymap, ), MenuItem::action("Open Project Settings", super::OpenProjectSettings), - MenuItem::action("Select Theme...", theme_selector::Toggle::default()), + MenuItem::action( + "Select Theme...", + zed_actions::theme_selector::Toggle::default(), + ), ], }), MenuItem::separator(), diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index b777f03646..0e62e88fea 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -50,6 +50,19 @@ pub mod command_palette { actions!(command_palette, [Toggle]); } +pub mod theme_selector { + use gpui::impl_actions; + use serde::Deserialize; + + #[derive(PartialEq, Clone, Default, Debug, Deserialize)] + pub struct Toggle { + /// A list of theme names to filter the theme selector down to. + pub themes_filter: Option>, + } + + impl_actions!(theme_selector, [Toggle]); +} + #[derive(Clone, Default, Deserialize, PartialEq)] pub struct InlineAssist { pub prompt: Option, From 4c7b48b35d7e04e82a4551ffa5cafa2b42a7f684 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Nov 2024 14:56:02 -0500 Subject: [PATCH 105/157] title_bar: Remove dependency on `vcs_menu` (#21011) This PR removes the `title_bar` crate's dependency on the `vcs_menu`. The `vcs_menu::OpenRecent` action now resides at `zed_actions::branches::OpenRecent`. Release Notes: - N/A --- Cargo.lock | 2 +- crates/title_bar/Cargo.toml | 1 - crates/title_bar/src/title_bar.rs | 7 +++---- crates/vcs_menu/Cargo.toml | 1 + crates/vcs_menu/src/lib.rs | 10 ++++------ crates/zed_actions/src/lib.rs | 6 ++++++ 6 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2bb4c4e0c7..f4b1662a01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12641,7 +12641,6 @@ dependencies = [ "tree-sitter-md", "ui", "util", - "vcs_menu", "windows 0.58.0", "workspace", "zed_actions", @@ -13653,6 +13652,7 @@ dependencies = [ "ui", "util", "workspace", + "zed_actions", ] [[package]] diff --git a/crates/title_bar/Cargo.toml b/crates/title_bar/Cargo.toml index 75cb49b5a8..8433ecad21 100644 --- a/crates/title_bar/Cargo.toml +++ b/crates/title_bar/Cargo.toml @@ -44,7 +44,6 @@ story = { workspace = true, optional = true } theme.workspace = true ui.workspace = true util.workspace = true -vcs_menu.workspace = true workspace.workspace = true zed_actions.workspace = true diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 744f4ce26d..4e9a99433a 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -27,7 +27,6 @@ use ui::{ IconSize, IconWithIndicator, Indicator, PopoverMenu, Tooltip, }; use util::ResultExt; -use vcs_menu::{BranchList, OpenRecent as ToggleVcsMenu}; use workspace::{notifications::NotifyResultExt, Workspace}; use zed_actions::{OpenBrowser, OpenRecent, OpenRemote}; @@ -442,14 +441,14 @@ impl TitleBar { .tooltip(move |cx| { Tooltip::with_meta( "Recent Branches", - Some(&ToggleVcsMenu), + Some(&zed_actions::branches::OpenRecent), "Local branches only", cx, ) }) .on_click(move |_, cx| { - let _ = workspace.update(cx, |this, cx| { - BranchList::open(this, &Default::default(), cx); + let _ = workspace.update(cx, |_this, cx| { + cx.dispatch_action(zed_actions::branches::OpenRecent.boxed_clone()); }); }), ) diff --git a/crates/vcs_menu/Cargo.toml b/crates/vcs_menu/Cargo.toml index 11de371868..47bf3d8984 100644 --- a/crates/vcs_menu/Cargo.toml +++ b/crates/vcs_menu/Cargo.toml @@ -18,3 +18,4 @@ project.workspace = true ui.workspace = true util.workspace = true workspace.workspace = true +zed_actions.workspace = true diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index f165c91bfe..f61bad57fa 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/crates/vcs_menu/src/lib.rs @@ -2,10 +2,9 @@ use anyhow::{anyhow, Context, Result}; use fuzzy::{StringMatch, StringMatchCandidate}; use git::repository::Branch; use gpui::{ - actions, rems, AnyElement, AppContext, AsyncAppContext, DismissEvent, EventEmitter, - FocusHandle, FocusableView, InteractiveElement, IntoElement, ParentElement, Render, - SharedString, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, - WindowContext, + rems, AnyElement, AppContext, AsyncAppContext, DismissEvent, EventEmitter, FocusHandle, + FocusableView, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, + Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext, }; use picker::{Picker, PickerDelegate}; use project::ProjectPath; @@ -14,8 +13,7 @@ use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::notifications::DetachAndPromptErr; use workspace::{ModalView, Workspace}; - -actions!(branches, [OpenRecent]); +use zed_actions::branches::OpenRecent; pub fn init(cx: &mut AppContext) { cx.observe_new_views(|workspace: &mut Workspace, _| { diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 0e62e88fea..848412c2a3 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -44,6 +44,12 @@ actions!( ] ); +pub mod branches { + use gpui::actions; + + actions!(branches, [OpenRecent]); +} + pub mod command_palette { use gpui::actions; From 614b3b979b7373aaa6dee84dfbc824fce1a86ea8 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Thu, 21 Nov 2024 20:03:50 +0000 Subject: [PATCH 106/157] macos: Add default keybind for ctrl-home / ctrl-end (#21007) This matches the default behavior on native macos apps. ctrl-fn-left == ctrl-home == MoveToBeginning ctrl-fn-right == ctrl-end == MoveToEnd --- assets/keymaps/default-macos.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 5b416db9b2..025ba4d69d 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -93,6 +93,8 @@ "ctrl-e": "editor::MoveToEndOfLine", "cmd-up": "editor::MoveToBeginning", "cmd-down": "editor::MoveToEnd", + "ctrl-home": "editor::MoveToBeginning", + "ctrl-end": "editor::MoveToEnd", "shift-up": "editor::SelectUp", "ctrl-shift-p": "editor::SelectUp", "shift-down": "editor::SelectDown", From 2868b67286b60e3b4f9126e3f146a218cfd962c0 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Nov 2024 15:24:04 -0500 Subject: [PATCH 107/157] title_bar: Remove dependency on `feedback` (#21013) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR removes the `title_bar` crate's dependency on the `feedback` crate. The `feedback::GiveFeedback` action now resides at `zed_actions::feedback::GiveFeedback`. `title_bar` now no longer depends on `editor` 🥳 Release Notes: - N/A --- Cargo.lock | 3 +-- crates/feedback/Cargo.toml | 3 ++- crates/feedback/src/feedback.rs | 2 -- crates/feedback/src/feedback_modal.rs | 3 ++- crates/title_bar/Cargo.toml | 3 --- crates/title_bar/src/application_menu.rs | 5 ++++- crates/zed/src/zed/app_menus.rs | 2 +- crates/zed_actions/src/lib.rs | 6 ++++++ 8 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4b1662a01..9ddbe6dfaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4347,6 +4347,7 @@ dependencies = [ "urlencoding", "util", "workspace", + "zed_actions", ] [[package]] @@ -12623,9 +12624,7 @@ dependencies = [ "call", "client", "collections", - "editor", "feature_flags", - "feedback", "gpui", "http_client", "notifications", diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index 0447858ca5..605b572c6c 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -22,8 +22,8 @@ db.workspace = true editor.workspace = true futures.workspace = true gpui.workspace = true -human_bytes = "0.4.1" http_client.workspace = true +human_bytes = "0.4.1" language.workspace = true log.workspace = true menu.workspace = true @@ -39,6 +39,7 @@ ui.workspace = true urlencoding = "2.1.2" util.workspace = true workspace.workspace = true +zed_actions.workspace = true [dev-dependencies] editor = { workspace = true, features = ["test-support"] } diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 671dea8689..f802a0950d 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -5,8 +5,6 @@ use workspace::Workspace; pub mod feedback_modal; -actions!(feedback, [GiveFeedback, SubmitFeedback]); - mod system_specs; actions!( diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 5270492aee..2c98267ccf 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -18,8 +18,9 @@ use serde_derive::Serialize; use ui::{prelude::*, Button, ButtonStyle, IconPosition, Tooltip}; use util::ResultExt; use workspace::{DismissDecision, ModalView, Workspace}; +use zed_actions::feedback::GiveFeedback; -use crate::{system_specs::SystemSpecs, GiveFeedback, OpenZedRepo}; +use crate::{system_specs::SystemSpecs, OpenZedRepo}; // For UI testing purposes const SEND_SUCCESS_IN_DEV_MODE: bool = true; diff --git a/crates/title_bar/Cargo.toml b/crates/title_bar/Cargo.toml index 8433ecad21..0a2878b357 100644 --- a/crates/title_bar/Cargo.toml +++ b/crates/title_bar/Cargo.toml @@ -19,7 +19,6 @@ test-support = [ "call/test-support", "client/test-support", "collections/test-support", - "editor/test-support", "gpui/test-support", "http_client/test-support", "project/test-support", @@ -31,7 +30,6 @@ test-support = [ auto_update.workspace = true call.workspace = true client.workspace = true -feedback.workspace = true feature_flags.workspace = true gpui.workspace = true notifications.workspace = true @@ -54,7 +52,6 @@ windows.workspace = true call = { workspace = true, features = ["test-support"] } client = { workspace = true, features = ["test-support"] } collections = { workspace = true, features = ["test-support"] } -editor = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } http_client = { workspace = true, features = ["test-support"] } notifications = { workspace = true, features = ["test-support"] } diff --git a/crates/title_bar/src/application_menu.rs b/crates/title_bar/src/application_menu.rs index 3d5a774e8f..ef13655bdb 100644 --- a/crates/title_bar/src/application_menu.rs +++ b/crates/title_bar/src/application_menu.rs @@ -116,7 +116,10 @@ impl Render for ApplicationMenu { url: "https://zed.dev/docs".into(), }), ) - .action("Give Feedback", Box::new(feedback::GiveFeedback)) + .action( + "Give Feedback", + Box::new(zed_actions::feedback::GiveFeedback), + ) .action("Check for Updates", Box::new(auto_update::Check)) .action("View Telemetry", Box::new(zed_actions::OpenTelemetryLog)) .action( diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index 3affa31986..8586df57f2 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -179,7 +179,7 @@ pub fn app_menus() -> Vec { MenuItem::action("View Telemetry", zed_actions::OpenTelemetryLog), MenuItem::action("View Dependency Licenses", zed_actions::OpenLicenses), MenuItem::action("Show Welcome", workspace::Welcome), - MenuItem::action("Give Feedback...", feedback::GiveFeedback), + MenuItem::action("Give Feedback...", zed_actions::feedback::GiveFeedback), MenuItem::separator(), MenuItem::action( "Documentation", diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 848412c2a3..b4bb6d2152 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -56,6 +56,12 @@ pub mod command_palette { actions!(command_palette, [Toggle]); } +pub mod feedback { + use gpui::actions; + + actions!(feedback, [GiveFeedback]); +} + pub mod theme_selector { use gpui::impl_actions; use serde::Deserialize; From 790fdcf737e8103140e654be8162c3f0c48f587c Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Nov 2024 15:48:35 -0500 Subject: [PATCH 108/157] collab_ui: Remove dependency on `vcs_menu` (#21016) This PR removes the `vcs_menu` dependency from `collab_ui`. We were only depending on this to call `vcs_menu::init`, which isn't necessary to do here. Release Notes: - N/A --- Cargo.lock | 2 +- crates/collab_ui/Cargo.toml | 3 +-- crates/collab_ui/src/collab_ui.rs | 1 - crates/zed/Cargo.toml | 5 +++-- crates/zed/src/main.rs | 1 + 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ddbe6dfaa..07af53a6c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2712,7 +2712,6 @@ dependencies = [ "tree-sitter-md", "ui", "util", - "vcs_menu", "workspace", ] @@ -15574,6 +15573,7 @@ dependencies = [ "urlencoding", "util", "uuid", + "vcs_menu", "vim", "welcome", "windows 0.58.0", diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index cd00e13206..3cc8f25b18 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -58,12 +58,11 @@ settings.workspace = true smallvec.workspace = true story = { workspace = true, optional = true } theme.workspace = true -time_format.workspace = true time.workspace = true +time_format.workspace = true title_bar.workspace = true ui.workspace = true util.workspace = true -vcs_menu.workspace = true workspace.workspace = true [dev-dependencies] diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 2baaa01490..67c4ad6dad 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -33,7 +33,6 @@ pub fn init(app_state: &Arc, cx: &mut AppContext) { notification_panel::init(cx); notifications::init(app_state, cx); title_bar::init(cx); - vcs_menu::init(cx); } fn notification_window_options( diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index b55ebce2b9..0eef53bd9e 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -15,11 +15,11 @@ name = "zed" path = "src/main.rs" [dependencies] -assistant_slash_command.workspace = true activity_indicator.workspace = true anyhow.workspace = true assets.workspace = true assistant.workspace = true +assistant_slash_command.workspace = true async-watch.workspace = true audio.workspace = true auto_update.workspace = true @@ -88,6 +88,7 @@ recent_projects.workspace = true release_channel.workspace = true remote.workspace = true repl.workspace = true +reqwest_client.workspace = true rope.workspace = true search.workspace = true serde.workspace = true @@ -112,11 +113,11 @@ theme_selector.workspace = true time.workspace = true toolchain_selector.workspace = true ui.workspace = true -reqwest_client.workspace = true url.workspace = true urlencoding = "2.1.2" util.workspace = true uuid.workspace = true +vcs_menu.workspace = true vim.workspace = true welcome.workspace = true workspace.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index f7aabb2626..b1b721c7c6 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -462,6 +462,7 @@ fn main() { call::init(app_state.client.clone(), app_state.user_store.clone(), cx); notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx); collab_ui::init(&app_state, cx); + vcs_menu::init(cx); feedback::init(cx); markdown_preview::init(cx); welcome::init(cx); From b102a40e045a96591b86a5f0171a28234b1f1434 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Nov 2024 16:24:38 -0500 Subject: [PATCH 109/157] Extract `VimModeSetting` to its own crate (#21019) This PR extracts the `VimModeSetting` out of the `vim` crate and into its own `vim_mode_setting` crate. A number of crates were depending on the entirety of the `vim` crate just to reference `VimModeSetting`, which was not ideal. Release Notes: - N/A --- Cargo.lock | 15 ++++++-- Cargo.toml | 2 ++ crates/extensions_ui/Cargo.toml | 2 +- crates/extensions_ui/src/extensions_ui.rs | 2 +- crates/vim/Cargo.toml | 1 + crates/vim/src/vim.rs | 25 ++----------- crates/vim_mode_setting/Cargo.toml | 17 +++++++++ crates/vim_mode_setting/LICENSE-GPL | 1 + .../vim_mode_setting/src/vim_mode_setting.rs | 36 +++++++++++++++++++ crates/welcome/Cargo.toml | 2 +- crates/welcome/src/welcome.rs | 2 +- crates/zed/Cargo.toml | 1 + crates/zed/src/zed.rs | 18 +++++----- 13 files changed, 85 insertions(+), 39 deletions(-) create mode 100644 crates/vim_mode_setting/Cargo.toml create mode 120000 crates/vim_mode_setting/LICENSE-GPL create mode 100644 crates/vim_mode_setting/src/vim_mode_setting.rs diff --git a/Cargo.lock b/Cargo.lock index 07af53a6c5..66155eda62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4236,7 +4236,7 @@ dependencies = [ "theme_selector", "ui", "util", - "vim", + "vim_mode_setting", "wasmtime-wasi", "workspace", "zed_actions", @@ -13697,10 +13697,20 @@ dependencies = [ "tokio", "ui", "util", + "vim_mode_setting", "workspace", "zed_actions", ] +[[package]] +name = "vim_mode_setting" +version = "0.1.0" +dependencies = [ + "anyhow", + "gpui", + "settings", +] + [[package]] name = "vscode_theme" version = "0.2.0" @@ -14415,7 +14425,7 @@ dependencies = [ "theme_selector", "ui", "util", - "vim", + "vim_mode_setting", "workspace", "zed_actions", ] @@ -15575,6 +15585,7 @@ dependencies = [ "uuid", "vcs_menu", "vim", + "vim_mode_setting", "welcome", "windows 0.58.0", "winresource", diff --git a/Cargo.toml b/Cargo.toml index b1feec52ef..58239800da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -129,6 +129,7 @@ members = [ "crates/util", "crates/vcs_menu", "crates/vim", + "crates/vim_mode_setting", "crates/welcome", "crates/workspace", "crates/worktree", @@ -304,6 +305,7 @@ ui_macros = { path = "crates/ui_macros" } util = { path = "crates/util" } vcs_menu = { path = "crates/vcs_menu" } vim = { path = "crates/vim" } +vim_mode_setting = { path = "crates/vim_mode_setting" } welcome = { path = "crates/welcome" } workspace = { path = "crates/workspace" } worktree = { path = "crates/worktree" } diff --git a/crates/extensions_ui/Cargo.toml b/crates/extensions_ui/Cargo.toml index 2ff2f21696..ce345ca2db 100644 --- a/crates/extensions_ui/Cargo.toml +++ b/crates/extensions_ui/Cargo.toml @@ -41,7 +41,7 @@ theme.workspace = true theme_selector.workspace = true ui.workspace = true util.workspace = true -vim.workspace = true +vim_mode_setting.workspace = true wasmtime-wasi.workspace = true workspace.workspace = true zed_actions.workspace = true diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 1586f3546e..ac2e147796 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -27,7 +27,7 @@ use release_channel::ReleaseChannel; use settings::Settings; use theme::ThemeSettings; use ui::{prelude::*, CheckboxWithLabel, ContextMenu, PopoverMenu, ToggleButton, Tooltip}; -use vim::VimModeSetting; +use vim_mode_setting::VimModeSetting; use workspace::{ item::{Item, ItemEvent}, Workspace, WorkspaceId, diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index fddb607c1f..ddf738d067 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -39,6 +39,7 @@ settings.workspace = true tokio = { version = "1.15", features = ["full"], optional = true } ui.workspace = true util.workspace = true +vim_mode_setting.workspace = true workspace.workspace = true zed_actions.workspace = true diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 77fc7db9d6..dd3bf297cb 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -41,15 +41,11 @@ use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals}; use std::{mem, ops::Range, sync::Arc}; use surrounds::SurroundsType; use ui::{IntoElement, VisualContext}; +use vim_mode_setting::VimModeSetting; use workspace::{self, Pane, Workspace}; use crate::state::ReplayableAction; -/// Whether or not to enable Vim mode. -/// -/// Default: false -pub struct VimModeSetting(pub bool); - /// An Action to Switch between modes #[derive(Clone, Deserialize, PartialEq)] pub struct SwitchMode(pub Mode); @@ -89,7 +85,7 @@ impl_actions!(vim, [SwitchMode, PushOperator, Number, SelectRegister]); /// Initializes the `vim` crate. pub fn init(cx: &mut AppContext) { - VimModeSetting::register(cx); + vim_mode_setting::init(cx); VimSettings::register(cx); VimGlobals::register(cx); @@ -1122,23 +1118,6 @@ impl Vim { } } -impl Settings for VimModeSetting { - const KEY: Option<&'static str> = Some("vim_mode"); - - type FileContent = Option; - - fn load(sources: SettingsSources, _: &mut AppContext) -> Result { - Ok(Self( - sources - .user - .or(sources.server) - .copied() - .flatten() - .unwrap_or(sources.default.ok_or_else(Self::missing_default)?), - )) - } -} - /// Controls when to use system clipboard. #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] diff --git a/crates/vim_mode_setting/Cargo.toml b/crates/vim_mode_setting/Cargo.toml new file mode 100644 index 0000000000..0c009fdfd6 --- /dev/null +++ b/crates/vim_mode_setting/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "vim_mode_setting" +version = "0.1.0" +edition = "2021" +publish = false +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/vim_mode_setting.rs" + +[dependencies] +anyhow.workspace = true +gpui.workspace = true +settings.workspace = true diff --git a/crates/vim_mode_setting/LICENSE-GPL b/crates/vim_mode_setting/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/vim_mode_setting/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/vim_mode_setting/src/vim_mode_setting.rs b/crates/vim_mode_setting/src/vim_mode_setting.rs new file mode 100644 index 0000000000..072db138df --- /dev/null +++ b/crates/vim_mode_setting/src/vim_mode_setting.rs @@ -0,0 +1,36 @@ +//! Contains the [`VimModeSetting`] used to enable/disable Vim mode. +//! +//! This is in its own crate as we want other crates to be able to enable or +//! disable Vim mode without having to depend on the `vim` crate in its +//! entirety. + +use anyhow::Result; +use gpui::AppContext; +use settings::{Settings, SettingsSources}; + +/// Initializes the `vim_mode_setting` crate. +pub fn init(cx: &mut AppContext) { + VimModeSetting::register(cx); +} + +/// Whether or not to enable Vim mode. +/// +/// Default: false +pub struct VimModeSetting(pub bool); + +impl Settings for VimModeSetting { + const KEY: Option<&'static str> = Some("vim_mode"); + + type FileContent = Option; + + fn load(sources: SettingsSources, _: &mut AppContext) -> Result { + Ok(Self( + sources + .user + .or(sources.server) + .copied() + .flatten() + .unwrap_or(sources.default.ok_or_else(Self::missing_default)?), + )) + } +} diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index 8ec245290d..26fe379ec6 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -30,7 +30,7 @@ settings.workspace = true theme_selector.workspace = true ui.workspace = true util.workspace = true -vim.workspace = true +vim_mode_setting.workspace = true workspace.workspace = true zed_actions.workspace = true diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 0d1e1c24d1..e66feec768 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -12,7 +12,7 @@ use gpui::{ use settings::{Settings, SettingsStore}; use std::sync::Arc; use ui::{prelude::*, CheckboxWithLabel}; -use vim::VimModeSetting; +use vim_mode_setting::VimModeSetting; use workspace::{ dock::DockPosition, item::{Item, ItemEvent}, diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 0eef53bd9e..6c447bcabe 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -119,6 +119,7 @@ util.workspace = true uuid.workspace = true vcs_menu.workspace = true vim.workspace = true +vim_mode_setting.workspace = true welcome.workspace = true workspace.workspace = true zed_actions.workspace = true diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 909afc207d..b2dbc087b0 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -9,7 +9,9 @@ mod open_listener; #[cfg(target_os = "windows")] pub(crate) mod windows_only_instance; +use anyhow::Context as _; pub use app_menus::*; +use assets::Assets; use assistant::PromptBuilder; use breadcrumbs::Breadcrumbs; use client::{zed_urls, ZED_URL_SCHEME}; @@ -18,17 +20,15 @@ use command_palette_hooks::CommandPaletteFilter; use editor::ProposedChangesEditorToolbar; use editor::{scroll::Autoscroll, Editor, MultiBuffer}; use feature_flags::FeatureFlagAppExt; +use futures::{channel::mpsc, select_biased, StreamExt}; use gpui::{ actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, MenuItem, PathPromptOptions, PromptLevel, ReadGlobal, Task, TitlebarOptions, View, ViewContext, VisualContext, WindowKind, WindowOptions, }; pub use open_listener::*; - -use anyhow::Context as _; -use assets::Assets; -use futures::{channel::mpsc, select_biased, StreamExt}; use outline_panel::OutlinePanel; +use paths::{local_settings_file_relative_path, local_tasks_file_relative_path}; use project::{DirectoryLister, Item}; use project_panel::ProjectPanel; use quick_action_bar::QuickActionBar; @@ -43,16 +43,14 @@ use settings::{ use std::any::TypeId; use std::path::PathBuf; use std::{borrow::Cow, ops::Deref, path::Path, sync::Arc}; -use theme::ActiveTheme; -use workspace::notifications::NotificationId; -use workspace::CloseIntent; - -use paths::{local_settings_file_relative_path, local_tasks_file_relative_path}; use terminal_view::terminal_panel::{self, TerminalPanel}; +use theme::ActiveTheme; use util::{asset_str, ResultExt}; use uuid::Uuid; -use vim::VimModeSetting; +use vim_mode_setting::VimModeSetting; use welcome::{BaseKeymap, MultibufferHint}; +use workspace::notifications::NotificationId; +use workspace::CloseIntent; use workspace::{ create_and_open_local_file, notifications::simple_message_notification::MessageNotification, open_new, AppState, NewFile, NewWindow, OpenLog, Toast, Workspace, WorkspaceSettings, From af34953bc33ffb085c3c59df1edbbfc105f49409 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Nov 2024 16:48:25 -0500 Subject: [PATCH 110/157] extensions_ui: Remove dependency on `theme_selector` (#21023) This PR removes the dependency on `theme_selector` from `extensions_ui`, as we can just dispatch the action instead. Release Notes: - N/A --- Cargo.lock | 1 - crates/extensions_ui/Cargo.toml | 1 - crates/extensions_ui/src/extensions_ui.rs | 19 +++++++++---------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66155eda62..34bccbc8b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4233,7 +4233,6 @@ dependencies = [ "smallvec", "snippet_provider", "theme", - "theme_selector", "ui", "util", "vim_mode_setting", diff --git a/crates/extensions_ui/Cargo.toml b/crates/extensions_ui/Cargo.toml index ce345ca2db..e8de7c3f12 100644 --- a/crates/extensions_ui/Cargo.toml +++ b/crates/extensions_ui/Cargo.toml @@ -38,7 +38,6 @@ settings.workspace = true smallvec.workspace = true snippet_provider.workspace = true theme.workspace = true -theme_selector.workspace = true ui.workspace = true util.workspace = true vim_mode_setting.workspace = true diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index ac2e147796..077d80b9b8 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -17,9 +17,9 @@ use editor::{Editor, EditorElement, EditorStyle}; use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ - actions, uniform_list, AppContext, EventEmitter, Flatten, FocusableView, InteractiveElement, - KeyContext, ParentElement, Render, Styled, Task, TextStyle, UniformListScrollHandle, View, - ViewContext, VisualContext, WeakView, WindowContext, + actions, uniform_list, Action, AppContext, EventEmitter, Flatten, FocusableView, + InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle, + UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext, }; use num_format::{Locale, ToFormattedString}; use project::DirectoryLister; @@ -254,14 +254,13 @@ impl ExtensionsPage { .collect::>(); if !themes.is_empty() { workspace - .update(cx, |workspace, cx| { - theme_selector::toggle( - workspace, - &zed_actions::theme_selector::Toggle { + .update(cx, |_workspace, cx| { + cx.dispatch_action( + zed_actions::theme_selector::Toggle { themes_filter: Some(themes), - }, - cx, - ) + } + .boxed_clone(), + ); }) .ok(); } From f74f670865956e95a4e852e248beb1b14df34008 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Thu, 21 Nov 2024 14:50:38 -0700 Subject: [PATCH 111/157] Fix panics from spawn_local tasks dropped on other threads in remote server (#21022) Closes #21020 Release Notes: - Fixed remote server panic of "local task dropped by a thread that didn't spawn it" --- crates/remote/src/ssh_session.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/remote/src/ssh_session.rs b/crates/remote/src/ssh_session.rs index d8c852c019..546135c30b 100644 --- a/crates/remote/src/ssh_session.rs +++ b/crates/remote/src/ssh_session.rs @@ -1074,7 +1074,7 @@ impl SshRemoteClient { c.connections.insert( opts.clone(), ConnectionPoolEntry::Connecting( - cx.foreground_executor() + cx.background_executor() .spawn({ let connection = connection.clone(); async move { Ok(connection.clone()) } From 72613b7668f2291cfc23d388795fc1eceaa991a1 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Thu, 21 Nov 2024 14:00:19 -0800 Subject: [PATCH 112/157] Implement RunningKernel trait for native and remote kernels (#20934) This PR introduces a unified interface for both native and remote kernels through the `RunningKernel` trait. When either the native kernel or the remote kernels are started, they return a `Box` to make it easier to work with the session. As a bonus of this refactor, I've dropped some of the mpsc channels to instead opt for passing messages directly to `session.route(message)`. There was a lot of simplification of `Session` by moving responsibilities to `NativeRunningKernel`. No release notes yet until this is finalized. * [x] Detect remote kernelspecs from configured remote servers * [x] Launch kernel on demand For now, this allows you to set env vars `JUPYTER_SERVER` and `JUPYTER_TOKEN` to access a remote server. `JUPYTER_SERVER` should be a base path like `http://localhost:8888` or `https://notebooks.gesis.org/binder/jupyter/user/rubydata-binder-w6igpy4l/` Release Notes: - N/A --- Cargo.lock | 1 + crates/repl/Cargo.toml | 2 + crates/repl/src/components/kernel_options.rs | 83 +++++-- crates/repl/src/kernels/mod.rs | 19 +- crates/repl/src/kernels/native_kernel.rs | 148 +++++++++--- crates/repl/src/kernels/remote_kernels.rs | 227 ++++++++++++++++--- crates/repl/src/repl_store.rs | 48 +++- crates/repl/src/session.rs | 180 ++++----------- 8 files changed, 478 insertions(+), 230 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34bccbc8b4..d950324d2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9926,6 +9926,7 @@ dependencies = [ "editor", "env_logger 0.11.5", "feature_flags", + "file_icons", "futures 0.3.31", "gpui", "http_client", diff --git a/crates/repl/Cargo.toml b/crates/repl/Cargo.toml index 60e8734771..293a58e762 100644 --- a/crates/repl/Cargo.toml +++ b/crates/repl/Cargo.toml @@ -22,8 +22,10 @@ collections.workspace = true command_palette_hooks.workspace = true editor.workspace = true feature_flags.workspace = true +file_icons.workspace = true futures.workspace = true gpui.workspace = true +http_client.workspace = true image.workspace = true jupyter-websocket-client.workspace = true jupyter-protocol.workspace = true diff --git a/crates/repl/src/components/kernel_options.rs b/crates/repl/src/components/kernel_options.rs index fc0213e54e..8fd9b412ea 100644 --- a/crates/repl/src/components/kernel_options.rs +++ b/crates/repl/src/components/kernel_options.rs @@ -34,6 +34,16 @@ pub struct KernelPickerDelegate { on_select: OnSelect, } +// Helper function to truncate long paths +fn truncate_path(path: &SharedString, max_length: usize) -> SharedString { + if path.len() <= max_length { + path.to_string().into() + } else { + let truncated = path.chars().rev().take(max_length - 3).collect::(); + format!("...{}", truncated.chars().rev().collect::()).into() + } +} + impl KernelSelector { pub fn new(on_select: OnSelect, worktree_id: WorktreeId, trigger: T) -> Self { KernelSelector { @@ -116,11 +126,25 @@ impl PickerDelegate for KernelPickerDelegate { &self, ix: usize, selected: bool, - _cx: &mut ViewContext>, + cx: &mut ViewContext>, ) -> Option { let kernelspec = self.filtered_kernels.get(ix)?; - let is_selected = self.selected_kernelspec.as_ref() == Some(kernelspec); + let icon = kernelspec.icon(cx); + + let (name, kernel_type, path_or_url) = match kernelspec { + KernelSpecification::Jupyter(_) => (kernelspec.name(), "Jupyter", None), + KernelSpecification::PythonEnv(_) => ( + kernelspec.name(), + "Python Env", + Some(truncate_path(&kernelspec.path(), 42)), + ), + KernelSpecification::Remote(_) => ( + kernelspec.name(), + "Remote", + Some(truncate_path(&kernelspec.path(), 42)), + ), + }; Some( ListItem::new(ix) @@ -128,25 +152,46 @@ impl PickerDelegate for KernelPickerDelegate { .spacing(ListItemSpacing::Sparse) .selected(selected) .child( - v_flex() - .min_w(px(600.)) + h_flex() .w_full() - .gap_0p5() + .gap_3() + .child(icon.color(Color::Default).size(IconSize::Medium)) .child( - h_flex() - .w_full() - .gap_1() - .child(Label::new(kernelspec.name()).weight(FontWeight::MEDIUM)) + v_flex() + .flex_grow() + .gap_0p5() .child( - Label::new(kernelspec.language()) - .size(LabelSize::Small) - .color(Color::Muted), + h_flex() + .justify_between() + .child( + div().w_48().text_ellipsis().child( + Label::new(name) + .weight(FontWeight::MEDIUM) + .size(LabelSize::Default), + ), + ) + .when_some(path_or_url.clone(), |flex, path| { + flex.text_ellipsis().child( + Label::new(path) + .size(LabelSize::Small) + .color(Color::Muted), + ) + }), + ) + .child( + h_flex() + .gap_1() + .child( + Label::new(kernelspec.language()) + .size(LabelSize::Small) + .color(Color::Muted), + ) + .child( + Label::new(kernel_type) + .size(LabelSize::Small) + .color(Color::Muted), + ), ), - ) - .child( - Label::new(kernelspec.path()) - .size(LabelSize::XSmall) - .color(Color::Muted), ), ) .when(is_selected, |item| { @@ -199,7 +244,9 @@ impl RenderOnce for KernelSelector { }; let picker_view = cx.new_view(|cx| { - let picker = Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into())); + let picker = Picker::uniform_list(delegate, cx) + .width(rems(30.)) + .max_height(Some(rems(20.).into())); picker }); diff --git a/crates/repl/src/kernels/mod.rs b/crates/repl/src/kernels/mod.rs index 3fe4c3c12d..47fde97154 100644 --- a/crates/repl/src/kernels/mod.rs +++ b/crates/repl/src/kernels/mod.rs @@ -6,7 +6,7 @@ use futures::{ future::Shared, stream, }; -use gpui::{AppContext, Model, Task}; +use gpui::{AppContext, Model, Task, WindowContext}; use language::LanguageName; pub use native_kernel::*; @@ -16,7 +16,7 @@ pub use remote_kernels::*; use anyhow::Result; use runtimelib::{ExecutionState, JupyterKernelspec, JupyterMessage, KernelInfoReply}; -use ui::SharedString; +use ui::{Icon, IconName, SharedString}; pub type JupyterMessageChannel = stream::SelectAll>; @@ -59,6 +59,19 @@ impl KernelSpecification { Self::Remote(spec) => spec.kernelspec.language.clone(), }) } + + pub fn icon(&self, cx: &AppContext) -> Icon { + let lang_name = match self { + Self::Jupyter(spec) => spec.kernelspec.language.clone(), + Self::PythonEnv(spec) => spec.kernelspec.language.clone(), + Self::Remote(spec) => spec.kernelspec.language.clone(), + }; + + file_icons::FileIcons::get(cx) + .get_type_icon(&lang_name.to_lowercase()) + .map(Icon::from_path) + .unwrap_or(Icon::new(IconName::ReplNeutral)) + } } pub fn python_env_kernel_specifications( @@ -134,7 +147,7 @@ pub trait RunningKernel: Send + Debug { fn set_execution_state(&mut self, state: ExecutionState); fn kernel_info(&self) -> Option<&KernelInfoReply>; fn set_kernel_info(&mut self, info: KernelInfoReply); - fn force_shutdown(&mut self) -> anyhow::Result<()>; + fn force_shutdown(&mut self, cx: &mut WindowContext) -> Task>; } #[derive(Debug, Clone)] diff --git a/crates/repl/src/kernels/native_kernel.rs b/crates/repl/src/kernels/native_kernel.rs index 03a57b34ef..6f7c5d92ee 100644 --- a/crates/repl/src/kernels/native_kernel.rs +++ b/crates/repl/src/kernels/native_kernel.rs @@ -1,10 +1,11 @@ use anyhow::{Context as _, Result}; use futures::{ channel::mpsc::{self}, + io::BufReader, stream::{SelectAll, StreamExt}, - SinkExt as _, + AsyncBufReadExt as _, SinkExt as _, }; -use gpui::{AppContext, EntityId, Task}; +use gpui::{EntityId, Task, View, WindowContext}; use jupyter_protocol::{JupyterMessage, JupyterMessageContent, KernelInfoReply}; use project::Fs; use runtimelib::{dirs, ConnectionInfo, ExecutionState, JupyterKernelspec}; @@ -18,7 +19,9 @@ use std::{ }; use uuid::Uuid; -use super::{JupyterMessageChannel, RunningKernel}; +use crate::Session; + +use super::RunningKernel; #[derive(Debug, Clone)] pub struct LocalKernelSpecification { @@ -83,10 +86,10 @@ async fn peek_ports(ip: IpAddr) -> Result<[u16; 5]> { pub struct NativeRunningKernel { pub process: smol::process::Child, _shell_task: Task>, - _iopub_task: Task>, _control_task: Task>, _routing_task: Task>, connection_path: PathBuf, + _process_status_task: Option>, pub working_directory: PathBuf, pub request_tx: mpsc::Sender, pub execution_state: ExecutionState, @@ -107,8 +110,10 @@ impl NativeRunningKernel { entity_id: EntityId, working_directory: PathBuf, fs: Arc, - cx: &mut AppContext, - ) -> Task> { + // todo: convert to weak view + session: View, + cx: &mut WindowContext, + ) -> Task>> { cx.spawn(|cx| async move { let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); let ports = peek_ports(ip).await?; @@ -136,7 +141,7 @@ impl NativeRunningKernel { let mut cmd = kernel_specification.command(&connection_path)?; - let process = cmd + let mut process = cmd .current_dir(&working_directory) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) @@ -155,8 +160,6 @@ impl NativeRunningKernel { let mut control_socket = runtimelib::create_client_control_connection(&connection_info, &session_id).await?; - let (mut iopub, iosub) = futures::channel::mpsc::channel(100); - let (request_tx, mut request_rx) = futures::channel::mpsc::channel::(100); @@ -164,18 +167,41 @@ impl NativeRunningKernel { let (mut shell_reply_tx, shell_reply_rx) = futures::channel::mpsc::channel(100); let mut messages_rx = SelectAll::new(); - messages_rx.push(iosub); messages_rx.push(control_reply_rx); messages_rx.push(shell_reply_rx); - let iopub_task = cx.background_executor().spawn({ - async move { - while let Ok(message) = iopub_socket.read().await { - iopub.send(message).await?; + cx.spawn({ + let session = session.clone(); + + |mut cx| async move { + while let Some(message) = messages_rx.next().await { + session + .update(&mut cx, |session, cx| { + session.route(&message, cx); + }) + .ok(); } anyhow::Ok(()) } - }); + }) + .detach(); + + // iopub task + cx.spawn({ + let session = session.clone(); + + |mut cx| async move { + while let Ok(message) = iopub_socket.read().await { + session + .update(&mut cx, |session, cx| { + session.route(&message, cx); + }) + .ok(); + } + anyhow::Ok(()) + } + }) + .detach(); let (mut control_request_tx, mut control_request_rx) = futures::channel::mpsc::channel(100); @@ -221,21 +247,74 @@ impl NativeRunningKernel { } }); - anyhow::Ok(( - Self { - process, - request_tx, - working_directory, - _shell_task: shell_task, - _iopub_task: iopub_task, - _control_task: control_task, - _routing_task: routing_task, - connection_path, - execution_state: ExecutionState::Idle, - kernel_info: None, - }, - messages_rx, - )) + let stderr = process.stderr.take(); + + cx.spawn(|mut _cx| async move { + if stderr.is_none() { + return; + } + let reader = BufReader::new(stderr.unwrap()); + let mut lines = reader.lines(); + while let Some(Ok(line)) = lines.next().await { + log::error!("kernel: {}", line); + } + }) + .detach(); + + let stdout = process.stdout.take(); + + cx.spawn(|mut _cx| async move { + if stdout.is_none() { + return; + } + let reader = BufReader::new(stdout.unwrap()); + let mut lines = reader.lines(); + while let Some(Ok(line)) = lines.next().await { + log::info!("kernel: {}", line); + } + }) + .detach(); + + let status = process.status(); + + let process_status_task = cx.spawn(|mut cx| async move { + let error_message = match status.await { + Ok(status) => { + if status.success() { + log::info!("kernel process exited successfully"); + return; + } + + format!("kernel process exited with status: {:?}", status) + } + Err(err) => { + format!("kernel process exited with error: {:?}", err) + } + }; + + log::error!("{}", error_message); + + session + .update(&mut cx, |session, cx| { + session.kernel_errored(error_message, cx); + + cx.notify(); + }) + .ok(); + }); + + anyhow::Ok(Box::new(Self { + process, + request_tx, + working_directory, + _process_status_task: Some(process_status_task), + _shell_task: shell_task, + _control_task: control_task, + _routing_task: routing_task, + connection_path, + execution_state: ExecutionState::Idle, + kernel_info: None, + }) as Box) }) } } @@ -265,14 +344,17 @@ impl RunningKernel for NativeRunningKernel { self.kernel_info = Some(info); } - fn force_shutdown(&mut self) -> anyhow::Result<()> { - match self.process.kill() { + fn force_shutdown(&mut self, _cx: &mut WindowContext) -> Task> { + self._process_status_task.take(); + self.request_tx.close_channel(); + + Task::ready(match self.process.kill() { Ok(_) => Ok(()), Err(error) => Err(anyhow::anyhow!( "Failed to kill the kernel process: {}", error )), - } + }) } } diff --git a/crates/repl/src/kernels/remote_kernels.rs b/crates/repl/src/kernels/remote_kernels.rs index 9d2d5f2810..808a7dbf02 100644 --- a/crates/repl/src/kernels/remote_kernels.rs +++ b/crates/repl/src/kernels/remote_kernels.rs @@ -1,12 +1,21 @@ -use futures::{channel::mpsc, StreamExt as _}; -use gpui::AppContext; +use futures::{channel::mpsc, SinkExt as _}; +use gpui::{Task, View, WindowContext}; +use http_client::{AsyncBody, HttpClient, Request}; use jupyter_protocol::{ExecutionState, JupyterMessage, KernelInfoReply}; -// todo(kyle): figure out if this needs to be different use runtimelib::JupyterKernelspec; +use futures::StreamExt; +use smol::io::AsyncReadExt as _; + +use crate::Session; + use super::RunningKernel; -use jupyter_websocket_client::RemoteServer; -use std::fmt::Debug; +use anyhow::Result; +use jupyter_websocket_client::{ + JupyterWebSocketReader, JupyterWebSocketWriter, KernelLaunchRequest, KernelSpecsResponse, + RemoteServer, +}; +use std::{fmt::Debug, sync::Arc}; #[derive(Debug, Clone)] pub struct RemoteKernelSpecification { @@ -16,6 +25,101 @@ pub struct RemoteKernelSpecification { pub kernelspec: JupyterKernelspec, } +pub async fn launch_remote_kernel( + remote_server: &RemoteServer, + http_client: Arc, + kernel_name: &str, + _path: &str, +) -> Result { + // + let kernel_launch_request = KernelLaunchRequest { + name: kernel_name.to_string(), + // todo: add path to runtimelib + // path, + }; + + let kernel_launch_request = serde_json::to_string(&kernel_launch_request)?; + + let request = Request::builder() + .method("POST") + .uri(&remote_server.api_url("/kernels")) + .header("Authorization", format!("token {}", remote_server.token)) + .body(AsyncBody::from(kernel_launch_request))?; + + let response = http_client.send(request).await?; + + if !response.status().is_success() { + let mut body = String::new(); + response.into_body().read_to_string(&mut body).await?; + return Err(anyhow::anyhow!("Failed to launch kernel: {}", body)); + } + + let mut body = String::new(); + response.into_body().read_to_string(&mut body).await?; + + let response: jupyter_websocket_client::Kernel = serde_json::from_str(&body)?; + + Ok(response.id) +} + +pub async fn list_remote_kernelspecs( + remote_server: RemoteServer, + http_client: Arc, +) -> Result> { + let url = remote_server.api_url("/kernelspecs"); + + let request = Request::builder() + .method("GET") + .uri(&url) + .header("Authorization", format!("token {}", remote_server.token)) + .body(AsyncBody::default())?; + + let response = http_client.send(request).await?; + + if response.status().is_success() { + let mut body = response.into_body(); + + let mut body_bytes = Vec::new(); + body.read_to_end(&mut body_bytes).await?; + + let kernel_specs: KernelSpecsResponse = serde_json::from_slice(&body_bytes)?; + + let remote_kernelspecs = kernel_specs + .kernelspecs + .into_iter() + .map(|(name, spec)| RemoteKernelSpecification { + name: name.clone(), + url: remote_server.base_url.clone(), + token: remote_server.token.clone(), + // todo: line up the jupyter kernelspec from runtimelib with + // the kernelspec pulled from the API + // + // There are _small_ differences, so we may just want a impl `From` + kernelspec: JupyterKernelspec { + argv: spec.spec.argv, + display_name: spec.spec.display_name, + language: spec.spec.language, + // todo: fix up mismatch in types here + metadata: None, + interrupt_mode: None, + env: None, + }, + }) + .collect::>(); + + if remote_kernelspecs.is_empty() { + Err(anyhow::anyhow!("No kernel specs found")) + } else { + Ok(remote_kernelspecs.clone()) + } + } else { + Err(anyhow::anyhow!( + "Failed to fetch kernel specs: {}", + response.status() + )) + } +} + impl PartialEq for RemoteKernelSpecification { fn eq(&self, other: &Self) -> bool { self.name == other.name && self.url == other.url @@ -26,55 +130,91 @@ impl Eq for RemoteKernelSpecification {} pub struct RemoteRunningKernel { remote_server: RemoteServer, + _receiving_task: Task>, + _routing_task: Task>, + http_client: Arc, pub working_directory: std::path::PathBuf, pub request_tx: mpsc::Sender, pub execution_state: ExecutionState, pub kernel_info: Option, + pub kernel_id: String, } impl RemoteRunningKernel { - pub async fn new( + pub fn new( kernelspec: RemoteKernelSpecification, working_directory: std::path::PathBuf, - request_tx: mpsc::Sender, - _cx: &mut AppContext, - ) -> anyhow::Result<( - Self, - (), // Stream - )> { + session: View, + cx: &mut WindowContext, + ) -> Task>> { let remote_server = RemoteServer { base_url: kernelspec.url, token: kernelspec.token, }; - // todo: launch a kernel to get a kernel ID - let kernel_id = "not-implemented"; + let http_client = cx.http_client(); - let kernel_socket = remote_server.connect_to_kernel(kernel_id).await?; + cx.spawn(|cx| async move { + let kernel_id = launch_remote_kernel( + &remote_server, + http_client.clone(), + &kernelspec.name, + working_directory.to_str().unwrap_or_default(), + ) + .await?; - let (mut _w, mut _r) = kernel_socket.split(); + let kernel_socket = remote_server.connect_to_kernel(&kernel_id).await?; - let (_messages_tx, _messages_rx) = mpsc::channel::(100); + let (mut w, mut r): (JupyterWebSocketWriter, JupyterWebSocketReader) = + kernel_socket.split(); - // let routing_task = cx.background_executor().spawn({ - // async move { - // while let Some(message) = request_rx.next().await { - // w.send(message).await; - // } - // } - // }); - // let messages_rx = r.into(); + let (request_tx, mut request_rx) = + futures::channel::mpsc::channel::(100); - anyhow::Ok(( - Self { + let routing_task = cx.background_executor().spawn({ + async move { + while let Some(message) = request_rx.next().await { + w.send(message).await.ok(); + } + Ok(()) + } + }); + + let receiving_task = cx.spawn({ + let session = session.clone(); + + |mut cx| async move { + while let Some(message) = r.next().await { + match message { + Ok(message) => { + session + .update(&mut cx, |session, cx| { + session.route(&message, cx); + }) + .ok(); + } + Err(e) => { + log::error!("Error receiving message: {:?}", e); + } + } + } + Ok(()) + } + }); + + anyhow::Ok(Box::new(Self { + _routing_task: routing_task, + _receiving_task: receiving_task, remote_server, working_directory, request_tx, + // todo(kyle): pull this from the kernel API to start with execution_state: ExecutionState::Idle, kernel_info: None, - }, - (), - )) + kernel_id, + http_client: http_client.clone(), + }) as Box) + }) } } @@ -116,7 +256,30 @@ impl RunningKernel for RemoteRunningKernel { self.kernel_info = Some(info); } - fn force_shutdown(&mut self) -> anyhow::Result<()> { - unimplemented!("force_shutdown") + fn force_shutdown(&mut self, cx: &mut WindowContext) -> Task> { + let url = self + .remote_server + .api_url(&format!("/kernels/{}", self.kernel_id)); + let token = self.remote_server.token.clone(); + let http_client = self.http_client.clone(); + + cx.spawn(|_| async move { + let request = Request::builder() + .method("DELETE") + .uri(&url) + .header("Authorization", format!("token {}", token)) + .body(AsyncBody::default())?; + + let response = http_client.send(request).await?; + + if response.status().is_success() { + Ok(()) + } else { + Err(anyhow::anyhow!( + "Failed to shutdown kernel: {}", + response.status() + )) + } + }) } } diff --git a/crates/repl/src/repl_store.rs b/crates/repl/src/repl_store.rs index a4863b809b..27854c0eee 100644 --- a/crates/repl/src/repl_store.rs +++ b/crates/repl/src/repl_store.rs @@ -7,11 +7,14 @@ use command_palette_hooks::CommandPaletteFilter; use gpui::{ prelude::*, AppContext, EntityId, Global, Model, ModelContext, Subscription, Task, View, }; +use jupyter_websocket_client::RemoteServer; use language::Language; use project::{Fs, Project, WorktreeId}; use settings::{Settings, SettingsStore}; -use crate::kernels::{local_kernel_specifications, python_env_kernel_specifications}; +use crate::kernels::{ + list_remote_kernelspecs, local_kernel_specifications, python_env_kernel_specifications, +}; use crate::{JupyterSettings, KernelSpecification, Session}; struct GlobalReplStore(Model); @@ -141,19 +144,50 @@ impl ReplStore { }) } + fn get_remote_kernel_specifications( + &self, + cx: &mut ModelContext, + ) -> Option>>> { + match ( + std::env::var("JUPYTER_SERVER"), + std::env::var("JUPYTER_TOKEN"), + ) { + (Ok(server), Ok(token)) => { + let remote_server = RemoteServer { + base_url: server, + token, + }; + let http_client = cx.http_client(); + Some(cx.spawn(|_, _| async move { + list_remote_kernelspecs(remote_server, http_client) + .await + .map(|specs| specs.into_iter().map(KernelSpecification::Remote).collect()) + })) + } + _ => None, + } + } + pub fn refresh_kernelspecs(&mut self, cx: &mut ModelContext) -> Task> { let local_kernel_specifications = local_kernel_specifications(self.fs.clone()); - cx.spawn(|this, mut cx| async move { - let local_kernel_specifications = local_kernel_specifications.await?; + let remote_kernel_specifications = self.get_remote_kernel_specifications(cx); - let mut kernel_options = Vec::new(); - for kernel_specification in local_kernel_specifications { - kernel_options.push(KernelSpecification::Jupyter(kernel_specification)); + cx.spawn(|this, mut cx| async move { + let mut all_specs = local_kernel_specifications + .await? + .into_iter() + .map(KernelSpecification::Jupyter) + .collect::>(); + + if let Some(remote_task) = remote_kernel_specifications { + if let Ok(remote_specs) = remote_task.await { + all_specs.extend(remote_specs); + } } this.update(&mut cx, |this, cx| { - this.kernel_specifications = kernel_options; + this.kernel_specifications = all_specs; cx.notify(); }) }) diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index 513e85719d..0c1dc287ed 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -1,4 +1,5 @@ use crate::components::KernelListItem; +use crate::kernels::RemoteRunningKernel; use crate::setup_editor_session_actions; use crate::{ kernels::{Kernel, KernelSpecification, NativeRunningKernel}, @@ -15,8 +16,7 @@ use editor::{ scroll::Autoscroll, Anchor, AnchorRangeExt as _, Editor, MultiBuffer, ToPoint, }; -use futures::io::BufReader; -use futures::{AsyncBufReadExt as _, FutureExt as _, StreamExt as _}; +use futures::FutureExt as _; use gpui::{ div, prelude::*, EventEmitter, Model, Render, Subscription, Task, View, ViewContext, WeakView, }; @@ -29,14 +29,13 @@ use runtimelib::{ use std::{env::temp_dir, ops::Range, sync::Arc, time::Duration}; use theme::ActiveTheme; use ui::{prelude::*, IconButtonShape, Tooltip}; +use util::ResultExt as _; pub struct Session { fs: Arc, editor: WeakView, pub kernel: Kernel, blocks: HashMap, - messaging_task: Option>, - process_status_task: Option>, pub kernel_specification: KernelSpecification, telemetry: Arc, _buffer_subscription: Subscription, @@ -219,8 +218,6 @@ impl Session { fs, editor, kernel: Kernel::StartingKernel(Task::ready(()).shared()), - messaging_task: None, - process_status_task: None, blocks: HashMap::default(), kernel_specification, _buffer_subscription: subscription, @@ -246,6 +243,8 @@ impl Session { cx.entity_id().to_string(), ); + let session_view = cx.view().clone(); + let kernel = match self.kernel_specification.clone() { KernelSpecification::Jupyter(kernel_specification) | KernelSpecification::PythonEnv(kernel_specification) => NativeRunningKernel::new( @@ -253,11 +252,15 @@ impl Session { entity_id, working_directory, self.fs.clone(), + session_view, + cx, + ), + KernelSpecification::Remote(remote_kernel_specification) => RemoteRunningKernel::new( + remote_kernel_specification, + working_directory, + session_view, cx, ), - KernelSpecification::Remote(_remote_kernel_specification) => { - unimplemented!() - } }; let pending_kernel = cx @@ -265,119 +268,15 @@ impl Session { let kernel = kernel.await; match kernel { - Ok((mut kernel, mut messages_rx)) => { + Ok(kernel) => { this.update(&mut cx, |session, cx| { - let stderr = kernel.process.stderr.take(); - - cx.spawn(|_session, mut _cx| async move { - if stderr.is_none() { - return; - } - let reader = BufReader::new(stderr.unwrap()); - let mut lines = reader.lines(); - while let Some(Ok(line)) = lines.next().await { - // todo!(): Log stdout and stderr to something the session can show - log::error!("kernel: {}", line); - } - }) - .detach(); - - let stdout = kernel.process.stdout.take(); - - cx.spawn(|_session, mut _cx| async move { - if stdout.is_none() { - return; - } - let reader = BufReader::new(stdout.unwrap()); - let mut lines = reader.lines(); - while let Some(Ok(line)) = lines.next().await { - log::info!("kernel: {}", line); - } - }) - .detach(); - - let status = kernel.process.status(); - session.kernel(Kernel::RunningKernel(Box::new(kernel)), cx); - - let process_status_task = cx.spawn(|session, mut cx| async move { - let error_message = match status.await { - Ok(status) => { - if status.success() { - log::info!("kernel process exited successfully"); - return; - } - - format!("kernel process exited with status: {:?}", status) - } - Err(err) => { - format!("kernel process exited with error: {:?}", err) - } - }; - - log::error!("{}", error_message); - - session - .update(&mut cx, |session, cx| { - session.kernel( - Kernel::ErroredLaunch(error_message.clone()), - cx, - ); - - session.blocks.values().for_each(|block| { - block.execution_view.update( - cx, - |execution_view, cx| { - match execution_view.status { - ExecutionStatus::Finished => { - // Do nothing when the output was good - } - _ => { - // All other cases, set the status to errored - execution_view.status = - ExecutionStatus::KernelErrored( - error_message.clone(), - ) - } - } - cx.notify(); - }, - ); - }); - - cx.notify(); - }) - .ok(); - }); - - session.process_status_task = Some(process_status_task); - - session.messaging_task = Some(cx.spawn(|session, mut cx| async move { - while let Some(message) = messages_rx.next().await { - session - .update(&mut cx, |session, cx| { - session.route(&message, cx); - }) - .ok(); - } - })); - - // todo!(@rgbkrk): send KernelInfoRequest once our shell channel read/writes are split - // cx.spawn(|this, mut cx| async move { - // cx.background_executor() - // .timer(Duration::from_millis(120)) - // .await; - // this.update(&mut cx, |this, cx| { - // this.send(KernelInfoRequest {}.into(), cx).ok(); - // }) - // .ok(); - // }) - // .detach(); + session.kernel(Kernel::RunningKernel(kernel), cx); }) .ok(); } Err(err) => { this.update(&mut cx, |session, cx| { - session.kernel(Kernel::ErroredLaunch(err.to_string()), cx); + session.kernel_errored(err.to_string(), cx); }) .ok(); } @@ -389,6 +288,26 @@ impl Session { cx.notify(); } + pub fn kernel_errored(&mut self, error_message: String, cx: &mut ViewContext) { + self.kernel(Kernel::ErroredLaunch(error_message.clone()), cx); + + self.blocks.values().for_each(|block| { + block.execution_view.update(cx, |execution_view, cx| { + match execution_view.status { + ExecutionStatus::Finished => { + // Do nothing when the output was good + } + _ => { + // All other cases, set the status to errored + execution_view.status = + ExecutionStatus::KernelErrored(error_message.clone()) + } + } + cx.notify(); + }); + }); + } + fn on_buffer_event( &mut self, buffer: Model, @@ -559,7 +478,7 @@ impl Session { } } - fn route(&mut self, message: &JupyterMessage, cx: &mut ViewContext) { + pub fn route(&mut self, message: &JupyterMessage, cx: &mut ViewContext) { let parent_message_id = match message.parent_header.as_ref() { Some(header) => &header.msg_id, None => return, @@ -639,21 +558,17 @@ impl Session { Kernel::RunningKernel(mut kernel) => { let mut request_tx = kernel.request_tx().clone(); + let forced = kernel.force_shutdown(cx); + cx.spawn(|this, mut cx| async move { let message: JupyterMessage = ShutdownRequest { restart: false }.into(); request_tx.try_send(message).ok(); + forced.await.log_err(); + // Give the kernel a bit of time to clean up cx.background_executor().timer(Duration::from_secs(3)).await; - this.update(&mut cx, |session, _cx| { - session.messaging_task.take(); - session.process_status_task.take(); - }) - .ok(); - - kernel.force_shutdown().ok(); - this.update(&mut cx, |session, cx| { session.clear_outputs(cx); session.kernel(Kernel::Shutdown, cx); @@ -664,8 +579,6 @@ impl Session { .detach(); } _ => { - self.messaging_task.take(); - self.process_status_task.take(); self.kernel(Kernel::Shutdown, cx); } } @@ -682,23 +595,19 @@ impl Session { Kernel::RunningKernel(mut kernel) => { let mut request_tx = kernel.request_tx().clone(); + let forced = kernel.force_shutdown(cx); + cx.spawn(|this, mut cx| async move { // Send shutdown request with restart flag log::debug!("restarting kernel"); let message: JupyterMessage = ShutdownRequest { restart: true }.into(); request_tx.try_send(message).ok(); - this.update(&mut cx, |session, _cx| { - session.messaging_task.take(); - session.process_status_task.take(); - }) - .ok(); - // Wait for kernel to shutdown cx.background_executor().timer(Duration::from_secs(1)).await; // Force kill the kernel if it hasn't shut down - kernel.force_shutdown().ok(); + forced.await.log_err(); // Start a new kernel this.update(&mut cx, |session, cx| { @@ -711,9 +620,6 @@ impl Session { .detach(); } _ => { - // If it's not already running, we can just clean up and start a new kernel - self.messaging_task.take(); - self.process_status_task.take(); self.clear_outputs(cx); self.start_kernel(cx); } From 5ee5a1a51e37751e368737f98e7bc53acf67b92f Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:16:49 +0100 Subject: [PATCH 113/157] chore: Do not produce universal binaries for our releases (#21014) Closes #ISSUE Release Notes: - We no longer provide universal binaries for our releases on macOS. --- .github/workflows/ci.yml | 9 +-------- script/bundle-mac | 18 ------------------ script/upload-nightly | 1 - 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f2f08aa1a..49881e2e7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -272,18 +272,12 @@ jobs: - name: Create macOS app bundle run: script/bundle-mac - - name: Rename single-architecture binaries + - name: Rename binaries if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }} run: | mv target/aarch64-apple-darwin/release/Zed.dmg target/aarch64-apple-darwin/release/Zed-aarch64.dmg mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg - - name: Upload app bundle (universal) to workflow run if main branch or specific label - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4 - if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }} - with: - name: Zed_${{ github.event.pull_request.head.sha || github.sha }}.dmg - path: target/release/Zed.dmg - name: Upload app bundle (aarch64) to workflow run if main branch or specific label uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4 if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }} @@ -309,7 +303,6 @@ jobs: target/zed-remote-server-macos-aarch64.gz target/aarch64-apple-darwin/release/Zed-aarch64.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg - target/release/Zed.dmg env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/script/bundle-mac b/script/bundle-mac index 06231a22ab..54247645cc 100755 --- a/script/bundle-mac +++ b/script/bundle-mac @@ -172,11 +172,6 @@ function download_git() { x86_64-apple-darwin) download_and_unpack "https://github.com/desktop/dugite-native/releases/download/${GIT_VERSION}/dugite-native-${GIT_VERSION}-${GIT_VERSION_SHA}-macOS-x64.tar.gz" bin/git ./git ;; - universal) - download_and_unpack "https://github.com/desktop/dugite-native/releases/download/${GIT_VERSION}/dugite-native-${GIT_VERSION}-${GIT_VERSION_SHA}-macOS-arm64.tar.gz" bin/git ./git_arm64 - download_and_unpack "https://github.com/desktop/dugite-native/releases/download/${GIT_VERSION}/dugite-native-${GIT_VERSION}-${GIT_VERSION_SHA}-macOS-x64.tar.gz" bin/git ./git_x64 - lipo -create ./git_arm64 ./git_x64 -output ./git - ;; *) echo "Unsupported architecture: $architecture" exit 1 @@ -377,20 +372,7 @@ else prepare_binaries "aarch64-apple-darwin" "$app_path_aarch64" prepare_binaries "x86_64-apple-darwin" "$app_path_x64" - cp -R "$app_path_x64" target/release/ - app_path=target/release/$(basename "$app_path_x64") - lipo \ - -create \ - target/{x86_64-apple-darwin,aarch64-apple-darwin}/${target_dir}/zed \ - -output \ - "${app_path}/Contents/MacOS/zed" - lipo \ - -create \ - target/{x86_64-apple-darwin,aarch64-apple-darwin}/${target_dir}/cli \ - -output \ - "${app_path}/Contents/MacOS/cli" - sign_app_binaries "$app_path" "universal" "." sign_app_binaries "$app_path_x64" "x86_64-apple-darwin" "x86_64-apple-darwin" sign_app_binaries "$app_path_aarch64" "aarch64-apple-darwin" "aarch64-apple-darwin" diff --git a/script/upload-nightly b/script/upload-nightly index fd37941981..87ad712ae4 100755 --- a/script/upload-nightly +++ b/script/upload-nightly @@ -43,7 +43,6 @@ case "$target" in macos) upload_to_blob_store $bucket_name "target/aarch64-apple-darwin/release/Zed.dmg" "nightly/Zed-aarch64.dmg" upload_to_blob_store $bucket_name "target/x86_64-apple-darwin/release/Zed.dmg" "nightly/Zed-x86_64.dmg" - upload_to_blob_store $bucket_name "target/release/Zed.dmg" "nightly/Zed.dmg" upload_to_blob_store $bucket_name "target/latest-sha" "nightly/latest-sha" rm -f "target/aarch64-apple-darwin/release/Zed.dmg" "target/x86_64-apple-darwin/release/Zed.dmg" "target/release/Zed.dmg" rm -f "target/latest-sha" From 9d95da56c34606388e12e5e814a3f9f5ec392369 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Nov 2024 17:50:22 -0500 Subject: [PATCH 114/157] welcome: Remove dependency on `theme_selector` (#21024) This PR removes the dependency on `theme_selector` from `welcome`, as we can just dispatch the action instead. Release Notes: - N/A --- Cargo.lock | 1 - crates/welcome/Cargo.toml | 1 - crates/welcome/src/welcome.rs | 10 +++------- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d950324d2f..89c5ae8180 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14422,7 +14422,6 @@ dependencies = [ "schemars", "serde", "settings", - "theme_selector", "ui", "util", "vim_mode_setting", diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index 26fe379ec6..473e5e853e 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -27,7 +27,6 @@ project.workspace = true schemars.workspace = true serde.workspace = true settings.workspace = true -theme_selector.workspace = true ui.workspace = true util.workspace = true vim_mode_setting.workspace = true diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index e66feec768..8dcb26bcc1 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -5,7 +5,7 @@ mod multibuffer_hint; use client::{telemetry::Telemetry, TelemetrySettings}; use db::kvp::KEY_VALUE_STORE; use gpui::{ - actions, svg, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement, + actions, svg, Action, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement, ParentElement, Render, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext, }; @@ -133,12 +133,8 @@ impl Render for WelcomePage { "welcome page: change theme".to_string(), ); this.workspace - .update(cx, |workspace, cx| { - theme_selector::toggle( - workspace, - &Default::default(), - cx, - ) + .update(cx, |_workspace, cx| { + cx.dispatch_action(zed_actions::theme_selector::Toggle::default().boxed_clone()); }) .ok(); })), From 0663bf2a5311a04e842ab95741e52fba8e417d17 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 22 Nov 2024 00:25:30 +0100 Subject: [PATCH 115/157] pylsp: Tweak default user settings (#21025) I've also looked into not creating temp dirs in project directories and succeeded at that for Mypy; no dice for rope though, I'll have to send a patch to pylsp to fix that. Closes #20646 Release Notes: - Python: tweaked default pylsp settings to be less noisy (mypy and pycodestyle are no longer enabled by default). --- crates/languages/src/python.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index df158b9c7d..429da01c8f 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -917,13 +917,17 @@ impl LspAdapter for PyLspAdapter { .unwrap_or_else(|| { json!({ "plugins": { - "rope_autoimport": {"enabled": true}, - "mypy": {"enabled": true} - } + "pycodestyle": {"enabled": false}, + "rope_autoimport": {"enabled": true, "memory": true}, + "mypy": {"enabled": false} + }, + "rope": { + "ropeFolder": null + }, }) }); - // If python.pythonPath is not set in user config, do so using our toolchain picker. + // If user did not explicitly modify their python venv, use one from picker. if let Some(toolchain) = toolchain { if user_settings.is_null() { user_settings = Value::Object(serde_json::Map::default()); @@ -939,23 +943,22 @@ impl LspAdapter for PyLspAdapter { .or_insert(Value::Object(serde_json::Map::default())) .as_object_mut() { - jedi.insert( - "environment".to_string(), - Value::String(toolchain.path.clone().into()), - ); + jedi.entry("environment".to_string()) + .or_insert_with(|| Value::String(toolchain.path.clone().into())); } if let Some(pylint) = python .entry("mypy") .or_insert(Value::Object(serde_json::Map::default())) .as_object_mut() { - pylint.insert( - "overrides".to_string(), + pylint.entry("overrides".to_string()).or_insert_with(|| { Value::Array(vec![ Value::String("--python-executable".into()), Value::String(toolchain.path.into()), - ]), - ); + Value::String("--cache-dir=/dev/null".into()), + Value::Bool(true), + ]) + }); } } } From 9211e699eef96fc4d67346ec0efac840f6b3aefb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Fri, 22 Nov 2024 07:32:49 +0800 Subject: [PATCH 116/157] Follow-up on #18447: Unintentional deletion during merge-conflicts resolution (#20991) After #18447 was merged, I reviewed the PR code as usual. During this review, I realized that some code was unintentionally removed when I was resolving merge conflicts in #18447. Sorry! Release Notes: - N/A --- crates/languages/src/go.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index 64583ad61f..b3073d7eaa 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -139,7 +139,8 @@ impl super::LspAdapter for GoLspAdapter { let gobin_dir = container_dir.join("gobin"); fs::create_dir_all(&gobin_dir).await?; - let install_output = util::command::new_smol_command("go") + let go = delegate.which("go".as_ref()).await.unwrap_or("go".into()); + let install_output = util::command::new_smol_command(go) .env("GO111MODULE", "on") .env("GOBIN", &gobin_dir) .args(["install", "golang.org/x/tools/gopls@latest"]) From e0245b3f3042610692e7223d09f6e6fa6d10098f Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Nov 2024 18:33:11 -0500 Subject: [PATCH 117/157] Merge `quick_action_bar` into `zed` (#21026) This PR merges the `quick_action_bar` crate into the `zed` crate. We weren't really gaining anything by having it be a separate crate, and it was introducing an additional step in the dependency graph that was getting in the way. It's only ~850 LOC, so the impact on the compilation speed of the `zed` crate itself is negligible. Release Notes: - N/A --- Cargo.lock | 20 +----------- Cargo.toml | 2 -- crates/quick_action_bar/Cargo.toml | 32 ------------------- crates/quick_action_bar/LICENSE-GPL | 1 - crates/zed/Cargo.toml | 2 +- crates/zed/src/zed.rs | 1 + .../src => zed/src/zed}/quick_action_bar.rs | 7 ++-- .../zed/quick_action_bar/markdown_preview.rs} | 2 +- .../src/zed/quick_action_bar}/repl_menu.rs | 5 ++- 9 files changed, 9 insertions(+), 63 deletions(-) delete mode 100644 crates/quick_action_bar/Cargo.toml delete mode 120000 crates/quick_action_bar/LICENSE-GPL rename crates/{quick_action_bar/src => zed/src/zed}/quick_action_bar.rs (99%) rename crates/{quick_action_bar/src/toggle_markdown_preview.rs => zed/src/zed/quick_action_bar/markdown_preview.rs} (98%) rename crates/{quick_action_bar/src => zed/src/zed/quick_action_bar}/repl_menu.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index 89c5ae8180..ddd2e400e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9426,24 +9426,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "quick_action_bar" -version = "0.1.0" -dependencies = [ - "assistant", - "editor", - "gpui", - "markdown_preview", - "picker", - "repl", - "search", - "settings", - "ui", - "util", - "workspace", - "zed_actions", -] - [[package]] name = "quinn" version = "0.11.6" @@ -15541,12 +15523,12 @@ dependencies = [ "outline_panel", "parking_lot", "paths", + "picker", "profiling", "project", "project_panel", "project_symbols", "proto", - "quick_action_bar", "recent_projects", "release_channel", "remote", diff --git a/Cargo.toml b/Cargo.toml index 58239800da..c12079a26a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,6 @@ members = [ "crates/project_panel", "crates/project_symbols", "crates/proto", - "crates/quick_action_bar", "crates/recent_projects", "crates/refineable", "crates/refineable/derive_refineable", @@ -259,7 +258,6 @@ project = { path = "crates/project" } project_panel = { path = "crates/project_panel" } project_symbols = { path = "crates/project_symbols" } proto = { path = "crates/proto" } -quick_action_bar = { path = "crates/quick_action_bar" } recent_projects = { path = "crates/recent_projects" } refineable = { path = "crates/refineable" } release_channel = { path = "crates/release_channel" } diff --git a/crates/quick_action_bar/Cargo.toml b/crates/quick_action_bar/Cargo.toml deleted file mode 100644 index b3228820f6..0000000000 --- a/crates/quick_action_bar/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "quick_action_bar" -version = "0.1.0" -edition = "2021" -publish = false -license = "GPL-3.0-or-later" - -[lints] -workspace = true - -[lib] -path = "src/quick_action_bar.rs" -doctest = false - -[dependencies] -assistant.workspace = true -editor.workspace = true -gpui.workspace = true -markdown_preview.workspace = true -repl.workspace = true -search.workspace = true -settings.workspace = true -ui.workspace = true -util.workspace = true -workspace.workspace = true -zed_actions.workspace = true -picker.workspace = true - -[dev-dependencies] -editor = { workspace = true, features = ["test-support"] } -gpui = { workspace = true, features = ["test-support"] } -workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/quick_action_bar/LICENSE-GPL b/crates/quick_action_bar/LICENSE-GPL deleted file mode 120000 index 89e542f750..0000000000 --- a/crates/quick_action_bar/LICENSE-GPL +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE-GPL \ No newline at end of file diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 6c447bcabe..52ec265480 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -78,12 +78,12 @@ outline.workspace = true outline_panel.workspace = true parking_lot.workspace = true paths.workspace = true +picker.workspace = true profiling.workspace = true project.workspace = true project_panel.workspace = true project_symbols.workspace = true proto.workspace = true -quick_action_bar.workspace = true recent_projects.workspace = true release_channel.workspace = true remote.workspace = true diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index b2dbc087b0..322ea3610b 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -6,6 +6,7 @@ pub(crate) mod linux_prompts; #[cfg(target_os = "macos")] pub(crate) mod mac_only_instance; mod open_listener; +mod quick_action_bar; #[cfg(target_os = "windows")] pub(crate) mod windows_only_instance; diff --git a/crates/quick_action_bar/src/quick_action_bar.rs b/crates/zed/src/zed/quick_action_bar.rs similarity index 99% rename from crates/quick_action_bar/src/quick_action_bar.rs rename to crates/zed/src/zed/quick_action_bar.rs index 7849620093..85090a1b97 100644 --- a/crates/quick_action_bar/src/quick_action_bar.rs +++ b/crates/zed/src/zed/quick_action_bar.rs @@ -1,3 +1,6 @@ +mod markdown_preview; +mod repl_menu; + use assistant::assistant_settings::AssistantSettings; use assistant::AssistantPanel; use editor::actions::{ @@ -6,7 +9,6 @@ use editor::actions::{ SelectNext, SelectSmallerSyntaxNode, ToggleGoToLine, ToggleOutline, }; use editor::{Editor, EditorSettings}; - use gpui::{ Action, AnchorCorner, ClickEvent, ElementId, EventEmitter, FocusHandle, FocusableView, InteractiveElement, ParentElement, Render, Styled, Subscription, View, ViewContext, WeakView, @@ -22,9 +24,6 @@ use workspace::{ }; use zed_actions::InlineAssist; -mod repl_menu; -mod toggle_markdown_preview; - pub struct QuickActionBar { _inlay_hints_enabled_subscription: Option, active_item: Option>, diff --git a/crates/quick_action_bar/src/toggle_markdown_preview.rs b/crates/zed/src/zed/quick_action_bar/markdown_preview.rs similarity index 98% rename from crates/quick_action_bar/src/toggle_markdown_preview.rs rename to crates/zed/src/zed/quick_action_bar/markdown_preview.rs index 527da3a568..5162cb0644 100644 --- a/crates/quick_action_bar/src/toggle_markdown_preview.rs +++ b/crates/zed/src/zed/quick_action_bar/markdown_preview.rs @@ -5,7 +5,7 @@ use markdown_preview::{ use ui::{prelude::*, text_for_keystroke, IconButtonShape, Tooltip}; use workspace::Workspace; -use crate::QuickActionBar; +use super::QuickActionBar; impl QuickActionBar { pub fn render_toggle_markdown_preview( diff --git a/crates/quick_action_bar/src/repl_menu.rs b/crates/zed/src/zed/quick_action_bar/repl_menu.rs similarity index 99% rename from crates/quick_action_bar/src/repl_menu.rs rename to crates/zed/src/zed/quick_action_bar/repl_menu.rs index b9ae940579..5f616da9d3 100644 --- a/crates/quick_action_bar/src/repl_menu.rs +++ b/crates/zed/src/zed/quick_action_bar/repl_menu.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use gpui::ElementId; use gpui::{percentage, Animation, AnimationExt, AnyElement, Transformation, View}; use picker::Picker; use repl::{ @@ -11,11 +12,9 @@ use ui::{ prelude::*, ButtonLike, ContextMenu, IconWithIndicator, Indicator, IntoElement, PopoverMenu, PopoverMenuHandle, Tooltip, }; - -use gpui::ElementId; use util::ResultExt; -use crate::QuickActionBar; +use super::QuickActionBar; const ZED_REPL_DOCUMENTATION: &str = "https://zed.dev/docs/repl"; From 6c470748ac481db9e31503d7349ead286f9886b3 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 21 Nov 2024 18:47:44 -0500 Subject: [PATCH 118/157] zed: Remove unnecessary `#[allow(non_snake_case)]` attribute (#21030) This PR removes the `#[allow(non_snake_case)]` attribute from the `zed` crate, as it wasn't actually doing anything. Release Notes: - N/A --- crates/zed/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index b1b721c7c6..e96a70f91d 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -1,5 +1,3 @@ -// Allow binary to be called Zed for a nice application menu when running executable directly -#![allow(non_snake_case)] // Disable command line from opening on release mode #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] From 477c6e6833b40a6789a819658ce55b4c02e96235 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 22 Nov 2024 01:13:48 +0100 Subject: [PATCH 119/157] pylsp: Update mypy plugin name (#21031) Follow-up to #21025 Release Notes: - N/A --- crates/languages/src/python.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 429da01c8f..2cedd704cf 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -919,7 +919,7 @@ impl LspAdapter for PyLspAdapter { "plugins": { "pycodestyle": {"enabled": false}, "rope_autoimport": {"enabled": true, "memory": true}, - "mypy": {"enabled": false} + "pylsp_mypy": {"enabled": false} }, "rope": { "ropeFolder": null @@ -947,7 +947,7 @@ impl LspAdapter for PyLspAdapter { .or_insert_with(|| Value::String(toolchain.path.clone().into())); } if let Some(pylint) = python - .entry("mypy") + .entry("pylsp_mypy") .or_insert(Value::Object(serde_json::Map::default())) .as_object_mut() { From 14ea4621ab98a315c4742c379723b8dbfd2087b9 Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Thu, 21 Nov 2024 19:21:18 -0700 Subject: [PATCH 120/157] Add `fs::MTime` newtype to encourage `!=` instead of `>` (#20830) See ["mtime comparison considered harmful"](https://apenwarr.ca/log/20181113) for details of why comparators other than equality/inequality should not be used with mtime. Release Notes: - N/A --- Cargo.lock | 3 + crates/assistant/src/context_store.rs | 2 +- crates/copilot/src/copilot.rs | 2 +- crates/editor/Cargo.toml | 1 + crates/editor/src/items.rs | 21 +-- crates/editor/src/persistence.rs | 24 ++-- crates/extension_host/src/extension_host.rs | 5 +- crates/fs/Cargo.toml | 1 + crates/fs/src/fs.rs | 136 ++++++++++++------- crates/language/Cargo.toml | 1 + crates/language/src/buffer.rs | 24 ++-- crates/semantic_index/src/embedding_index.rs | 14 +- crates/semantic_index/src/summary_backlog.rs | 9 +- crates/semantic_index/src/summary_index.rs | 18 +-- crates/worktree/src/worktree.rs | 6 +- 15 files changed, 155 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ddd2e400e7..75d69fdcf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3731,6 +3731,7 @@ dependencies = [ "emojis", "env_logger 0.11.5", "file_icons", + "fs", "futures 0.3.31", "fuzzy", "git", @@ -4621,6 +4622,7 @@ dependencies = [ "objc", "parking_lot", "paths", + "proto", "rope", "serde", "serde_json", @@ -6487,6 +6489,7 @@ dependencies = [ "ctor", "ec4rs", "env_logger 0.11.5", + "fs", "futures 0.3.31", "fuzzy", "git", diff --git a/crates/assistant/src/context_store.rs b/crates/assistant/src/context_store.rs index 568b04e492..217d59faa4 100644 --- a/crates/assistant/src/context_store.rs +++ b/crates/assistant/src/context_store.rs @@ -770,7 +770,7 @@ impl ContextStore { contexts.push(SavedContextMetadata { title: title.to_string(), path, - mtime: metadata.mtime.into(), + mtime: metadata.mtime.timestamp_for_user().into(), }); } } diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index 7ea289706c..bc424d2d5a 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -1231,7 +1231,7 @@ mod tests { fn disk_state(&self) -> language::DiskState { language::DiskState::Present { - mtime: std::time::UNIX_EPOCH, + mtime: ::fs::MTime::from_seconds_and_nanos(100, 42), } } diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 8d03fa79f0..f1f1b34981 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -42,6 +42,7 @@ emojis.workspace = true file_icons.workspace = true futures.workspace = true fuzzy.workspace = true +fs.workspace = true git.workspace = true gpui.workspace = true http_client.workspace = true diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index bd54d2c376..51ad9b9dec 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1618,15 +1618,14 @@ fn path_for_file<'a>( #[cfg(test)] mod tests { use crate::editor_tests::init_test; + use fs::Fs; use super::*; + use fs::MTime; use gpui::{AppContext, VisualTestContext}; use language::{LanguageMatcher, TestFile}; use project::FakeFs; - use std::{ - path::{Path, PathBuf}, - time::SystemTime, - }; + use std::path::{Path, PathBuf}; #[gpui::test] fn test_path_for_file(cx: &mut AppContext) { @@ -1679,9 +1678,7 @@ mod tests { async fn test_deserialize(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); - let now = SystemTime::now(); let fs = FakeFs::new(cx.executor()); - fs.set_next_mtime(now); fs.insert_file("/file.rs", Default::default()).await; // Test case 1: Deserialize with path and contents @@ -1690,12 +1687,18 @@ mod tests { let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx)); let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); let item_id = 1234 as ItemId; + let mtime = fs + .metadata(Path::new("/file.rs")) + .await + .unwrap() + .unwrap() + .mtime; let serialized_editor = SerializedEditor { abs_path: Some(PathBuf::from("/file.rs")), contents: Some("fn main() {}".to_string()), language: Some("Rust".to_string()), - mtime: Some(now), + mtime: Some(mtime), }; DB.save_serialized_editor(item_id, workspace_id, serialized_editor.clone()) @@ -1792,9 +1795,7 @@ mod tests { let workspace_id = workspace::WORKSPACE_DB.next_id().await.unwrap(); let item_id = 9345 as ItemId; - let old_mtime = now - .checked_sub(std::time::Duration::from_secs(60 * 60 * 24)) - .unwrap(); + let old_mtime = MTime::from_seconds_and_nanos(0, 50); let serialized_editor = SerializedEditor { abs_path: Some(PathBuf::from("/file.rs")), contents: Some("fn main() {}".to_string()), diff --git a/crates/editor/src/persistence.rs b/crates/editor/src/persistence.rs index a52fb60543..06e2ea1f9b 100644 --- a/crates/editor/src/persistence.rs +++ b/crates/editor/src/persistence.rs @@ -1,8 +1,8 @@ use anyhow::Result; use db::sqlez::bindable::{Bind, Column, StaticColumnCount}; use db::sqlez::statement::Statement; +use fs::MTime; use std::path::PathBuf; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; use db::sqlez_macros::sql; use db::{define_connection, query}; @@ -14,7 +14,7 @@ pub(crate) struct SerializedEditor { pub(crate) abs_path: Option, pub(crate) contents: Option, pub(crate) language: Option, - pub(crate) mtime: Option, + pub(crate) mtime: Option, } impl StaticColumnCount for SerializedEditor { @@ -29,16 +29,13 @@ impl Bind for SerializedEditor { let start_index = statement.bind(&self.contents, start_index)?; let start_index = statement.bind(&self.language, start_index)?; - let mtime = self.mtime.and_then(|mtime| { - mtime - .duration_since(UNIX_EPOCH) - .ok() - .map(|duration| (duration.as_secs() as i64, duration.subsec_nanos() as i32)) - }); - let start_index = match mtime { + let start_index = match self + .mtime + .and_then(|mtime| mtime.to_seconds_and_nanos_for_persistence()) + { Some((seconds, nanos)) => { - let start_index = statement.bind(&seconds, start_index)?; - statement.bind(&nanos, start_index)? + let start_index = statement.bind(&(seconds as i64), start_index)?; + statement.bind(&(nanos as i32), start_index)? } None => { let start_index = statement.bind::>(&None, start_index)?; @@ -64,7 +61,7 @@ impl Column for SerializedEditor { let mtime = mtime_seconds .zip(mtime_nanos) - .map(|(seconds, nanos)| UNIX_EPOCH + Duration::new(seconds as u64, nanos as u32)); + .map(|(seconds, nanos)| MTime::from_seconds_and_nanos(seconds as u64, nanos as u32)); let editor = Self { abs_path, @@ -280,12 +277,11 @@ mod tests { assert_eq!(have, serialized_editor); // Storing and retrieving mtime - let now = SystemTime::now(); let serialized_editor = SerializedEditor { abs_path: None, contents: None, language: None, - mtime: Some(now), + mtime: Some(MTime::from_seconds_and_nanos(100, 42)), }; DB.save_serialized_editor(1234, workspace_id, serialized_editor.clone()) diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index a858123fd9..4a832faeff 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -345,7 +345,10 @@ impl ExtensionStore { if let (Ok(Some(index_metadata)), Ok(Some(extensions_metadata))) = (index_metadata, extensions_metadata) { - if index_metadata.mtime > extensions_metadata.mtime { + if index_metadata + .mtime + .bad_is_greater_than(extensions_metadata.mtime) + { extension_index_needs_rebuild = false; } } diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index a9dbb751b6..7a1cfaeaa5 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -24,6 +24,7 @@ libc.workspace = true parking_lot.workspace = true paths.workspace = true rope.workspace = true +proto.workspace = true serde.workspace = true serde_json.workspace = true smol.workspace = true diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 268a9d3f32..fc0fae3fe8 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -27,13 +27,14 @@ use futures::{future::BoxFuture, AsyncRead, Stream, StreamExt}; use git::repository::{GitRepository, RealGitRepository}; use gpui::{AppContext, Global, ReadGlobal}; use rope::Rope; +use serde::{Deserialize, Serialize}; use smol::io::AsyncWriteExt; use std::{ io::{self, Write}, path::{Component, Path, PathBuf}, pin::Pin, sync::Arc, - time::{Duration, SystemTime}, + time::{Duration, SystemTime, UNIX_EPOCH}, }; use tempfile::{NamedTempFile, TempDir}; use text::LineEnding; @@ -179,13 +180,62 @@ pub struct RemoveOptions { #[derive(Copy, Clone, Debug)] pub struct Metadata { pub inode: u64, - pub mtime: SystemTime, + pub mtime: MTime, pub is_symlink: bool, pub is_dir: bool, pub len: u64, pub is_fifo: bool, } +/// Filesystem modification time. The purpose of this newtype is to discourage use of operations +/// that do not make sense for mtimes. In particular, it is not always valid to compare mtimes using +/// `<` or `>`, as there are many things that can cause the mtime of a file to be earlier than it +/// was. See ["mtime comparison considered harmful" - apenwarr](https://apenwarr.ca/log/20181113). +/// +/// Do not derive Ord, PartialOrd, or arithmetic operation traits. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[serde(transparent)] +pub struct MTime(SystemTime); + +impl MTime { + /// Conversion intended for persistence and testing. + pub fn from_seconds_and_nanos(secs: u64, nanos: u32) -> Self { + MTime(UNIX_EPOCH + Duration::new(secs, nanos)) + } + + /// Conversion intended for persistence. + pub fn to_seconds_and_nanos_for_persistence(self) -> Option<(u64, u32)> { + self.0 + .duration_since(UNIX_EPOCH) + .ok() + .map(|duration| (duration.as_secs(), duration.subsec_nanos())) + } + + /// Returns the value wrapped by this `MTime`, for presentation to the user. The name including + /// "_for_user" is to discourage misuse - this method should not be used when making decisions + /// about file dirtiness. + pub fn timestamp_for_user(self) -> SystemTime { + self.0 + } + + /// Temporary method to split out the behavior changes from introduction of this newtype. + pub fn bad_is_greater_than(self, other: MTime) -> bool { + self.0 > other.0 + } +} + +impl From for MTime { + fn from(timestamp: proto::Timestamp) -> Self { + MTime(timestamp.into()) + } +} + +impl From for proto::Timestamp { + fn from(mtime: MTime) -> Self { + mtime.0.into() + } +} + #[derive(Default)] pub struct RealFs { git_hosting_provider_registry: Arc, @@ -558,7 +608,7 @@ impl Fs for RealFs { Ok(Some(Metadata { inode, - mtime: metadata.modified().unwrap(), + mtime: MTime(metadata.modified().unwrap()), len: metadata.len(), is_symlink, is_dir: metadata.file_type().is_dir(), @@ -818,13 +868,13 @@ struct FakeFsState { enum FakeFsEntry { File { inode: u64, - mtime: SystemTime, + mtime: MTime, len: u64, content: Vec, }, Dir { inode: u64, - mtime: SystemTime, + mtime: MTime, len: u64, entries: BTreeMap>>, git_repo_state: Option>>, @@ -836,6 +886,18 @@ enum FakeFsEntry { #[cfg(any(test, feature = "test-support"))] impl FakeFsState { + fn get_and_increment_mtime(&mut self) -> MTime { + let mtime = self.next_mtime; + self.next_mtime += FakeFs::SYSTEMTIME_INTERVAL; + MTime(mtime) + } + + fn get_and_increment_inode(&mut self) -> u64 { + let inode = self.next_inode; + self.next_inode += 1; + inode + } + fn read_path(&self, target: &Path) -> Result>> { Ok(self .try_read_path(target, true) @@ -959,7 +1021,7 @@ pub static FS_DOT_GIT: std::sync::LazyLock<&'static OsStr> = impl FakeFs { /// We need to use something large enough for Windows and Unix to consider this a new file. /// https://doc.rust-lang.org/nightly/std/time/struct.SystemTime.html#platform-specific-behavior - const SYSTEMTIME_INTERVAL: u64 = 100; + const SYSTEMTIME_INTERVAL: Duration = Duration::from_nanos(100); pub fn new(executor: gpui::BackgroundExecutor) -> Arc { let (tx, mut rx) = smol::channel::bounded::(10); @@ -969,13 +1031,13 @@ impl FakeFs { state: Mutex::new(FakeFsState { root: Arc::new(Mutex::new(FakeFsEntry::Dir { inode: 0, - mtime: SystemTime::UNIX_EPOCH, + mtime: MTime(UNIX_EPOCH), len: 0, entries: Default::default(), git_repo_state: None, })), git_event_tx: tx, - next_mtime: SystemTime::UNIX_EPOCH, + next_mtime: UNIX_EPOCH + Self::SYSTEMTIME_INTERVAL, next_inode: 1, event_txs: Default::default(), buffered_events: Vec::new(), @@ -1007,13 +1069,16 @@ impl FakeFs { state.next_mtime = next_mtime; } + pub fn get_and_increment_mtime(&self) -> MTime { + let mut state = self.state.lock(); + state.get_and_increment_mtime() + } + pub async fn touch_path(&self, path: impl AsRef) { let mut state = self.state.lock(); let path = path.as_ref(); - let new_mtime = state.next_mtime; - let new_inode = state.next_inode; - state.next_inode += 1; - state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL); + let new_mtime = state.get_and_increment_mtime(); + let new_inode = state.get_and_increment_inode(); state .write_path(path, move |entry| { match entry { @@ -1062,19 +1127,14 @@ impl FakeFs { fn write_file_internal(&self, path: impl AsRef, content: Vec) -> Result<()> { let mut state = self.state.lock(); - let path = path.as_ref(); - let inode = state.next_inode; - let mtime = state.next_mtime; - state.next_inode += 1; - state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL); let file = Arc::new(Mutex::new(FakeFsEntry::File { - inode, - mtime, + inode: state.get_and_increment_inode(), + mtime: state.get_and_increment_mtime(), len: content.len() as u64, content, })); let mut kind = None; - state.write_path(path, { + state.write_path(path.as_ref(), { let kind = &mut kind; move |entry| { match entry { @@ -1090,7 +1150,7 @@ impl FakeFs { Ok(()) } })?; - state.emit_event([(path, kind)]); + state.emit_event([(path.as_ref(), kind)]); Ok(()) } @@ -1383,16 +1443,6 @@ impl FakeFsEntry { } } - fn set_file_content(&mut self, path: &Path, new_content: Vec) -> Result<()> { - if let Self::File { content, mtime, .. } = self { - *mtime = SystemTime::now(); - *content = new_content; - Ok(()) - } else { - Err(anyhow!("not a file: {}", path.display())) - } - } - fn dir_entries( &mut self, path: &Path, @@ -1456,10 +1506,8 @@ impl Fs for FakeFs { } let mut state = self.state.lock(); - let inode = state.next_inode; - let mtime = state.next_mtime; - state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL); - state.next_inode += 1; + let inode = state.get_and_increment_inode(); + let mtime = state.get_and_increment_mtime(); state.write_path(&cur_path, |entry| { entry.or_insert_with(|| { created_dirs.push((cur_path.clone(), Some(PathEventKind::Created))); @@ -1482,10 +1530,8 @@ impl Fs for FakeFs { async fn create_file(&self, path: &Path, options: CreateOptions) -> Result<()> { self.simulate_random_delay().await; let mut state = self.state.lock(); - let inode = state.next_inode; - let mtime = state.next_mtime; - state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL); - state.next_inode += 1; + let inode = state.get_and_increment_inode(); + let mtime = state.get_and_increment_mtime(); let file = Arc::new(Mutex::new(FakeFsEntry::File { inode, mtime, @@ -1625,13 +1671,12 @@ impl Fs for FakeFs { let source = normalize_path(source); let target = normalize_path(target); let mut state = self.state.lock(); - let mtime = state.next_mtime; - let inode = util::post_inc(&mut state.next_inode); - state.next_mtime += Duration::from_nanos(Self::SYSTEMTIME_INTERVAL); + let mtime = state.get_and_increment_mtime(); + let inode = state.get_and_increment_inode(); let source_entry = state.read_path(&source)?; let content = source_entry.lock().file_content(&source)?.clone(); let mut kind = Some(PathEventKind::Created); - let entry = state.write_path(&target, |e| match e { + state.write_path(&target, |e| match e { btree_map::Entry::Occupied(e) => { if options.overwrite { kind = Some(PathEventKind::Changed); @@ -1647,14 +1692,11 @@ impl Fs for FakeFs { inode, mtime, len: content.len() as u64, - content: Vec::new(), + content, }))) .clone(), )), })?; - if let Some(entry) = entry { - entry.lock().set_file_content(&target, content)?; - } state.emit_event([(target, kind)]); Ok(()) } diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 41285d8222..8b97d4a95f 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -31,6 +31,7 @@ async-watch.workspace = true clock.workspace = true collections.workspace = true ec4rs.workspace = true +fs.workspace = true futures.workspace = true fuzzy.workspace = true git.workspace = true diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d1a01c26e6..2479eafd7a 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -21,6 +21,7 @@ use async_watch as watch; use clock::Lamport; pub use clock::ReplicaId; use collections::HashMap; +use fs::MTime; use futures::channel::oneshot; use gpui::{ AnyElement, AppContext, Context as _, EventEmitter, HighlightStyle, Model, ModelContext, @@ -51,7 +52,7 @@ use std::{ path::{Path, PathBuf}, str, sync::{Arc, LazyLock}, - time::{Duration, Instant, SystemTime}, + time::{Duration, Instant}, vec, }; use sum_tree::TreeMap; @@ -108,7 +109,7 @@ pub struct Buffer { file: Option>, /// The mtime of the file when this buffer was last loaded from /// or saved to disk. - saved_mtime: Option, + saved_mtime: Option, /// The version vector when this buffer was last loaded from /// or saved to disk. saved_version: clock::Global, @@ -406,22 +407,19 @@ pub trait File: Send + Sync { /// modified. In the case where the file is not stored, it can be either `New` or `Deleted`. In the /// UI these two states are distinguished. For example, the buffer tab does not display a deletion /// indicator for new files. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum DiskState { /// File created in Zed that has not been saved. New, /// File present on the filesystem. - Present { - /// Last known mtime (modification time). - mtime: SystemTime, - }, + Present { mtime: MTime }, /// Deleted file that was previously present. Deleted, } impl DiskState { /// Returns the file's last known modification time on disk. - pub fn mtime(self) -> Option { + pub fn mtime(self) -> Option { match self { DiskState::New => None, DiskState::Present { mtime } => Some(mtime), @@ -976,7 +974,7 @@ impl Buffer { } /// The mtime of the buffer's file when the buffer was last saved or reloaded from disk. - pub fn saved_mtime(&self) -> Option { + pub fn saved_mtime(&self) -> Option { self.saved_mtime } @@ -1011,7 +1009,7 @@ impl Buffer { pub fn did_save( &mut self, version: clock::Global, - mtime: Option, + mtime: Option, cx: &mut ModelContext, ) { self.saved_version = version; @@ -1077,7 +1075,7 @@ impl Buffer { &mut self, version: clock::Global, line_ending: LineEnding, - mtime: Option, + mtime: Option, cx: &mut ModelContext, ) { self.saved_version = version; @@ -1777,7 +1775,9 @@ impl Buffer { match file.disk_state() { DiskState::New => false, DiskState::Present { mtime } => match self.saved_mtime { - Some(saved_mtime) => mtime > saved_mtime && self.has_unsaved_edits(), + Some(saved_mtime) => { + mtime.bad_is_greater_than(saved_mtime) && self.has_unsaved_edits() + } None => true, }, DiskState::Deleted => true, diff --git a/crates/semantic_index/src/embedding_index.rs b/crates/semantic_index/src/embedding_index.rs index 0913124341..4e3d74a2ea 100644 --- a/crates/semantic_index/src/embedding_index.rs +++ b/crates/semantic_index/src/embedding_index.rs @@ -7,6 +7,7 @@ use anyhow::{anyhow, Context as _, Result}; use collections::Bound; use feature_flags::FeatureFlagAppExt; use fs::Fs; +use fs::MTime; use futures::stream::StreamExt; use futures_batch::ChunksTimeoutStreamExt; use gpui::{AppContext, Model, Task}; @@ -17,14 +18,7 @@ use project::{Entry, UpdatedEntriesSet, Worktree}; use serde::{Deserialize, Serialize}; use smol::channel; use smol::future::FutureExt; -use std::{ - cmp::Ordering, - future::Future, - iter, - path::Path, - sync::Arc, - time::{Duration, SystemTime}, -}; +use std::{cmp::Ordering, future::Future, iter, path::Path, sync::Arc, time::Duration}; use util::ResultExt; use worktree::Snapshot; @@ -451,7 +445,7 @@ struct ChunkFiles { pub struct ChunkedFile { pub path: Arc, - pub mtime: Option, + pub mtime: Option, pub handle: IndexingEntryHandle, pub text: String, pub chunks: Vec, @@ -465,7 +459,7 @@ pub struct EmbedFiles { #[derive(Debug, Serialize, Deserialize)] pub struct EmbeddedFile { pub path: Arc, - pub mtime: Option, + pub mtime: Option, pub chunks: Vec, } diff --git a/crates/semantic_index/src/summary_backlog.rs b/crates/semantic_index/src/summary_backlog.rs index c6d8e33a45..e77fa4862f 100644 --- a/crates/semantic_index/src/summary_backlog.rs +++ b/crates/semantic_index/src/summary_backlog.rs @@ -1,5 +1,6 @@ use collections::HashMap; -use std::{path::Path, sync::Arc, time::SystemTime}; +use fs::MTime; +use std::{path::Path, sync::Arc}; const MAX_FILES_BEFORE_RESUMMARIZE: usize = 4; const MAX_BYTES_BEFORE_RESUMMARIZE: u64 = 1_000_000; // 1 MB @@ -7,14 +8,14 @@ const MAX_BYTES_BEFORE_RESUMMARIZE: u64 = 1_000_000; // 1 MB #[derive(Default, Debug)] pub struct SummaryBacklog { /// Key: path to a file that needs summarization, but that we haven't summarized yet. Value: that file's size on disk, in bytes, and its mtime. - files: HashMap, (u64, Option)>, + files: HashMap, (u64, Option)>, /// Cache of the sum of all values in `files`, so we don't have to traverse the whole map to check if we're over the byte limit. total_bytes: u64, } impl SummaryBacklog { /// Store the given path in the backlog, along with how many bytes are in it. - pub fn insert(&mut self, path: Arc, bytes_on_disk: u64, mtime: Option) { + pub fn insert(&mut self, path: Arc, bytes_on_disk: u64, mtime: Option) { let (prev_bytes, _) = self .files .insert(path, (bytes_on_disk, mtime)) @@ -34,7 +35,7 @@ impl SummaryBacklog { /// Remove all the entries in the backlog and return the file paths as an iterator. #[allow(clippy::needless_lifetimes)] // Clippy thinks this 'a can be elided, but eliding it gives a compile error - pub fn drain<'a>(&'a mut self) -> impl Iterator, Option)> + 'a { + pub fn drain<'a>(&'a mut self) -> impl Iterator, Option)> + 'a { self.total_bytes = 0; self.files diff --git a/crates/semantic_index/src/summary_index.rs b/crates/semantic_index/src/summary_index.rs index 1cbb670397..44cac88564 100644 --- a/crates/semantic_index/src/summary_index.rs +++ b/crates/semantic_index/src/summary_index.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Context as _, Result}; use arrayvec::ArrayString; -use fs::Fs; +use fs::{Fs, MTime}; use futures::{stream::StreamExt, TryFutureExt}; use futures_batch::ChunksTimeoutStreamExt; use gpui::{AppContext, Model, Task}; @@ -21,7 +21,7 @@ use std::{ future::Future, path::Path, sync::Arc, - time::{Duration, Instant, SystemTime}, + time::{Duration, Instant}, }; use util::ResultExt; use worktree::Snapshot; @@ -39,7 +39,7 @@ struct UnsummarizedFile { // Path to the file on disk path: Arc, // The mtime of the file on disk - mtime: Option, + mtime: Option, // BLAKE3 hash of the source file's contents digest: Blake3Digest, // The source file's contents @@ -51,7 +51,7 @@ struct SummarizedFile { // Path to the file on disk path: String, // The mtime of the file on disk - mtime: Option, + mtime: Option, // BLAKE3 hash of the source file's contents digest: Blake3Digest, // The LLM's summary of the file's contents @@ -63,7 +63,7 @@ pub type Blake3Digest = ArrayString<{ blake3::OUT_LEN * 2 }>; #[derive(Debug, Serialize, Deserialize)] pub struct FileDigest { - pub mtime: Option, + pub mtime: Option, pub digest: Blake3Digest, } @@ -88,7 +88,7 @@ pub struct SummaryIndex { } struct Backlogged { - paths_to_digest: channel::Receiver, Option)>>, + paths_to_digest: channel::Receiver, Option)>>, task: Task>, } @@ -319,7 +319,7 @@ impl SummaryIndex { digest_db: heed::Database>, txn: &RoTxn<'_>, entry: &Entry, - ) -> Vec<(Arc, Option)> { + ) -> Vec<(Arc, Option)> { let entry_db_key = db_key_for_path(&entry.path); match digest_db.get(&txn, &entry_db_key) { @@ -414,7 +414,7 @@ impl SummaryIndex { fn digest_files( &self, - paths: channel::Receiver, Option)>>, + paths: channel::Receiver, Option)>>, worktree_abs_path: Arc, cx: &AppContext, ) -> MightNeedSummaryFiles { @@ -646,7 +646,7 @@ impl SummaryIndex { let start = Instant::now(); let backlogged = { let (tx, rx) = channel::bounded(512); - let needs_summary: Vec<(Arc, Option)> = { + let needs_summary: Vec<(Arc, Option)> = { let mut backlog = self.backlog.lock(); backlog.drain().collect() diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index bf072ca549..b7ee4466c7 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -7,7 +7,7 @@ use ::ignore::gitignore::{Gitignore, GitignoreBuilder}; use anyhow::{anyhow, Context as _, Result}; use clock::ReplicaId; use collections::{HashMap, HashSet, VecDeque}; -use fs::{copy_recursive, Fs, PathEvent, RemoveOptions, Watcher}; +use fs::{copy_recursive, Fs, MTime, PathEvent, RemoveOptions, Watcher}; use futures::{ channel::{ mpsc::{self, UnboundedSender}, @@ -61,7 +61,7 @@ use std::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, - time::{Duration, Instant, SystemTime}, + time::{Duration, Instant}, }; use sum_tree::{Bias, Edit, SeekTarget, SumTree, TreeMap, TreeSet}; use text::{LineEnding, Rope}; @@ -3395,7 +3395,7 @@ pub struct Entry { pub kind: EntryKind, pub path: Arc, pub inode: u64, - pub mtime: Option, + pub mtime: Option, pub canonical_path: Option>, /// Whether this entry is ignored by Git. From 933c11a9b2c071b7ab8465542fcec3f824a6cee0 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 21 Nov 2024 23:06:03 -0500 Subject: [PATCH 121/157] Remove dead snowflake code (#21041) Co-authored-by: Nathan Sobo <1789+nathansobo@users.noreply.github.com> Release Notes: - N/A --- crates/collab/src/api/events.rs | 45 --------------------------------- 1 file changed, 45 deletions(-) diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 2679193cad..3cda6a397a 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -1588,48 +1588,3 @@ struct SnowflakeRow { pub user_properties: Option, pub insert_id: Option, } - -#[derive(Serialize, Deserialize)] -struct SnowflakeData { - /// Identifier unique to each Zed installation (differs for stable, preview, dev) - pub installation_id: Option, - /// Identifier unique to each logged in Zed user (randomly generated on first sign in) - /// Identifier unique to each Zed session (differs for each time you open Zed) - pub session_id: Option, - pub metrics_id: Option, - /// True for Zed staff, otherwise false - pub is_staff: Option, - /// Zed version number - pub app_version: String, - pub os_name: String, - pub os_version: Option, - pub architecture: String, - /// Zed release channel (stable, preview, dev) - pub release_channel: Option, - pub signed_in: bool, - - #[serde(flatten)] - pub editor_event: Option, - #[serde(flatten)] - pub inline_completion_event: Option, - #[serde(flatten)] - pub call_event: Option, - #[serde(flatten)] - pub assistant_event: Option, - #[serde(flatten)] - pub cpu_event: Option, - #[serde(flatten)] - pub memory_event: Option, - #[serde(flatten)] - pub app_event: Option, - #[serde(flatten)] - pub setting_event: Option, - #[serde(flatten)] - pub extension_event: Option, - #[serde(flatten)] - pub edit_event: Option, - #[serde(flatten)] - pub repl_event: Option, - #[serde(flatten)] - pub action_event: Option, -} From 114c4621433eb948b87fa5f4f41df8af6601f66e Mon Sep 17 00:00:00 2001 From: tims <0xtimsb@gmail.com> Date: Fri, 22 Nov 2024 16:29:04 +0530 Subject: [PATCH 122/157] Maintain selection on file/dir deletion in project panel (#20577) Closes #20444 - Focus on next file/dir on deletion. - Focus on prev file/dir in case where it's last item in worktree. - Tested when multiple files/dirs are being deleted. Release Notes: - Maintain selection on file/dir deletion in project panel. --------- Co-authored-by: Kirill Bulatov --- crates/project_panel/src/project_panel.rs | 800 +++++++++++++++++++++- crates/util/src/paths.rs | 32 +- crates/workspace/src/pane.rs | 2 +- 3 files changed, 813 insertions(+), 21 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 5ad2c2d12e..c757924727 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -40,6 +40,7 @@ use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use std::{ cell::OnceCell, + cmp, collections::HashSet, ffi::OsStr, ops::Range, @@ -53,7 +54,7 @@ use ui::{ IndentGuideColors, IndentGuideLayout, KeyBinding, Label, ListItem, Scrollbar, ScrollbarState, Tooltip, }; -use util::{maybe, ResultExt, TryFutureExt}; +use util::{maybe, paths::compare_paths, ResultExt, TryFutureExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, notifications::{DetachAndPromptErr, NotifyTaskExt}, @@ -550,7 +551,7 @@ impl ProjectPanel { .entry((project_path.worktree_id, path_buffer.clone())) .and_modify(|strongest_diagnostic_severity| { *strongest_diagnostic_severity = - std::cmp::min(*strongest_diagnostic_severity, diagnostic_severity); + cmp::min(*strongest_diagnostic_severity, diagnostic_severity); }) .or_insert(diagnostic_severity); } @@ -1184,15 +1185,15 @@ impl ProjectPanel { fn remove(&mut self, trash: bool, skip_prompt: bool, cx: &mut ViewContext<'_, ProjectPanel>) { maybe!({ - if self.marked_entries.is_empty() && self.selection.is_none() { + let items_to_delete = self.disjoint_entries_for_removal(cx); + if items_to_delete.is_empty() { return None; } let project = self.project.read(cx); - let items_to_delete = self.marked_entries(); let mut dirty_buffers = 0; let file_paths = items_to_delete - .into_iter() + .iter() .filter_map(|selection| { let project_path = project.path_for_entry(selection.entry_id, cx)?; dirty_buffers += @@ -1261,28 +1262,120 @@ impl ProjectPanel { } else { None }; - - cx.spawn(|this, mut cx| async move { + let next_selection = self.find_next_selection_after_deletion(items_to_delete, cx); + cx.spawn(|panel, mut cx| async move { if let Some(answer) = answer { if answer.await != Ok(0) { - return Result::<(), anyhow::Error>::Ok(()); + return anyhow::Ok(()); } } for (entry_id, _) in file_paths { - this.update(&mut cx, |this, cx| { - this.project - .update(cx, |project, cx| project.delete_entry(entry_id, trash, cx)) - .ok_or_else(|| anyhow!("no such entry")) - })?? - .await?; + panel + .update(&mut cx, |panel, cx| { + panel + .project + .update(cx, |project, cx| project.delete_entry(entry_id, trash, cx)) + .context("no such entry") + })?? + .await?; } - Result::<(), anyhow::Error>::Ok(()) + panel.update(&mut cx, |panel, cx| { + if let Some(next_selection) = next_selection { + panel.selection = Some(next_selection); + panel.autoscroll(cx); + } else { + panel.select_last(&SelectLast {}, cx); + } + })?; + Ok(()) }) .detach_and_log_err(cx); Some(()) }); } + fn find_next_selection_after_deletion( + &self, + sanitized_entries: BTreeSet, + cx: &mut ViewContext, + ) -> Option { + if sanitized_entries.is_empty() { + return None; + } + + let project = self.project.read(cx); + let (worktree_id, worktree) = sanitized_entries + .iter() + .map(|entry| entry.worktree_id) + .filter_map(|id| project.worktree_for_id(id, cx).map(|w| (id, w.read(cx)))) + .max_by(|(_, a), (_, b)| a.root_name().cmp(b.root_name()))?; + + let marked_entries_in_worktree = sanitized_entries + .iter() + .filter(|e| e.worktree_id == worktree_id) + .collect::>(); + let latest_entry = marked_entries_in_worktree + .iter() + .max_by(|a, b| { + match ( + worktree.entry_for_id(a.entry_id), + worktree.entry_for_id(b.entry_id), + ) { + (Some(a), Some(b)) => { + compare_paths((&a.path, a.is_file()), (&b.path, b.is_file())) + } + _ => cmp::Ordering::Equal, + } + }) + .and_then(|e| worktree.entry_for_id(e.entry_id))?; + + let parent_path = latest_entry.path.parent()?; + let parent_entry = worktree.entry_for_path(parent_path)?; + + // Remove all siblings that are being deleted except the last marked entry + let mut siblings: Vec = worktree + .snapshot() + .child_entries(parent_path) + .filter(|sibling| { + sibling.id == latest_entry.id + || !marked_entries_in_worktree.contains(&&SelectedEntry { + worktree_id, + entry_id: sibling.id, + }) + }) + .cloned() + .collect(); + + project::sort_worktree_entries(&mut siblings); + let sibling_entry_index = siblings + .iter() + .position(|sibling| sibling.id == latest_entry.id)?; + + if let Some(next_sibling) = sibling_entry_index + .checked_add(1) + .and_then(|i| siblings.get(i)) + { + return Some(SelectedEntry { + worktree_id, + entry_id: next_sibling.id, + }); + } + if let Some(prev_sibling) = sibling_entry_index + .checked_sub(1) + .and_then(|i| siblings.get(i)) + { + return Some(SelectedEntry { + worktree_id, + entry_id: prev_sibling.id, + }); + } + // No neighbour sibling found, fall back to parent + Some(SelectedEntry { + worktree_id, + entry_id: parent_entry.id, + }) + } + fn unfold_directory(&mut self, _: &UnfoldDirectory, cx: &mut ViewContext) { if let Some((worktree, entry)) = self.selected_entry(cx) { self.unfolded_dir_ids.insert(entry.id); @@ -1835,6 +1928,54 @@ impl ProjectPanel { None } + fn disjoint_entries_for_removal(&self, cx: &AppContext) -> BTreeSet { + let marked_entries = self.marked_entries(); + let mut sanitized_entries = BTreeSet::new(); + if marked_entries.is_empty() { + return sanitized_entries; + } + + let project = self.project.read(cx); + let marked_entries_by_worktree: HashMap> = marked_entries + .into_iter() + .filter(|entry| !project.entry_is_worktree_root(entry.entry_id, cx)) + .fold(HashMap::default(), |mut map, entry| { + map.entry(entry.worktree_id).or_default().push(entry); + map + }); + + for (worktree_id, marked_entries) in marked_entries_by_worktree { + if let Some(worktree) = project.worktree_for_id(worktree_id, cx) { + let worktree = worktree.read(cx); + let marked_dir_paths = marked_entries + .iter() + .filter_map(|entry| { + worktree.entry_for_id(entry.entry_id).and_then(|entry| { + if entry.is_dir() { + Some(entry.path.as_ref()) + } else { + None + } + }) + }) + .collect::>(); + + sanitized_entries.extend(marked_entries.into_iter().filter(|entry| { + let Some(entry_info) = worktree.entry_for_id(entry.entry_id) else { + return false; + }; + let entry_path = entry_info.path.as_ref(); + let inside_marked_dir = marked_dir_paths.iter().any(|&marked_dir_path| { + entry_path != marked_dir_path && entry_path.starts_with(marked_dir_path) + }); + !inside_marked_dir + })); + } + } + + sanitized_entries + } + // Returns list of entries that should be affected by an operation. // When currently selected entry is not marked, it's treated as the only marked entry. fn marked_entries(&self) -> BTreeSet { @@ -5080,14 +5221,13 @@ mod tests { &[ "v src", " v test", - " second.rs", + " second.rs <== selected", " third.rs" ], "Project panel should have no deleted file, no other file is selected in it" ); ensure_no_open_items_and_panes(&workspace, cx); - select_path(&panel, "src/test/second.rs", cx); panel.update(cx, |panel, cx| panel.open(&Open, cx)); cx.executor().run_until_parked(); assert_eq!( @@ -5121,7 +5261,7 @@ mod tests { submit_deletion_skipping_prompt(&panel, cx); assert_eq!( visible_entries_as_strings(&panel, 0..10, cx), - &["v src", " v test", " third.rs"], + &["v src", " v test", " third.rs <== selected"], "Project panel should have no deleted file, with one last file remaining" ); ensure_no_open_items_and_panes(&workspace, cx); @@ -5630,7 +5770,11 @@ mod tests { submit_deletion(&panel, cx); assert_eq!( visible_entries_as_strings(&panel, 0..10, cx), - &["v project_root", " v dir_1", " v nested_dir",] + &[ + "v project_root", + " v dir_1", + " v nested_dir <== selected", + ] ); } #[gpui::test] @@ -6327,6 +6471,598 @@ mod tests { ); } + #[gpui::test] + async fn test_basic_file_deletion_scenarios(cx: &mut gpui::TestAppContext) { + init_test_with_editor(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/root", + json!({ + "dir1": { + "subdir1": {}, + "file1.txt": "", + "file2.txt": "", + }, + "dir2": { + "subdir2": {}, + "file3.txt": "", + "file4.txt": "", + }, + "file5.txt": "", + "file6.txt": "", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/root".as_ref()], cx).await; + let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let cx = &mut VisualTestContext::from_window(*workspace, cx); + let panel = workspace.update(cx, ProjectPanel::new).unwrap(); + + toggle_expand_dir(&panel, "root/dir1", cx); + toggle_expand_dir(&panel, "root/dir2", cx); + + // Test Case 1: Delete middle file in directory + select_path(&panel, "root/dir1/file1.txt", cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..15, cx), + &[ + "v root", + " v dir1", + " > subdir1", + " file1.txt <== selected", + " file2.txt", + " v dir2", + " > subdir2", + " file3.txt", + " file4.txt", + " file5.txt", + " file6.txt", + ], + "Initial state before deleting middle file" + ); + + submit_deletion(&panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..15, cx), + &[ + "v root", + " v dir1", + " > subdir1", + " file2.txt <== selected", + " v dir2", + " > subdir2", + " file3.txt", + " file4.txt", + " file5.txt", + " file6.txt", + ], + "Should select next file after deleting middle file" + ); + + // Test Case 2: Delete last file in directory + submit_deletion(&panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..15, cx), + &[ + "v root", + " v dir1", + " > subdir1 <== selected", + " v dir2", + " > subdir2", + " file3.txt", + " file4.txt", + " file5.txt", + " file6.txt", + ], + "Should select next directory when last file is deleted" + ); + + // Test Case 3: Delete root level file + select_path(&panel, "root/file6.txt", cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..15, cx), + &[ + "v root", + " v dir1", + " > subdir1", + " v dir2", + " > subdir2", + " file3.txt", + " file4.txt", + " file5.txt", + " file6.txt <== selected", + ], + "Initial state before deleting root level file" + ); + + submit_deletion(&panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..15, cx), + &[ + "v root", + " v dir1", + " > subdir1", + " v dir2", + " > subdir2", + " file3.txt", + " file4.txt", + " file5.txt <== selected", + ], + "Should select prev entry at root level" + ); + } + + #[gpui::test] + async fn test_complex_selection_scenarios(cx: &mut gpui::TestAppContext) { + init_test_with_editor(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/root", + json!({ + "dir1": { + "subdir1": { + "a.txt": "", + "b.txt": "" + }, + "file1.txt": "", + }, + "dir2": { + "subdir2": { + "c.txt": "", + "d.txt": "" + }, + "file2.txt": "", + }, + "file3.txt": "", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/root".as_ref()], cx).await; + let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let cx = &mut VisualTestContext::from_window(*workspace, cx); + let panel = workspace.update(cx, ProjectPanel::new).unwrap(); + + toggle_expand_dir(&panel, "root/dir1", cx); + toggle_expand_dir(&panel, "root/dir1/subdir1", cx); + toggle_expand_dir(&panel, "root/dir2", cx); + toggle_expand_dir(&panel, "root/dir2/subdir2", cx); + + // Test Case 1: Select and delete nested directory with parent + cx.simulate_modifiers_change(gpui::Modifiers { + control: true, + ..Default::default() + }); + select_path_with_mark(&panel, "root/dir1/subdir1", cx); + select_path_with_mark(&panel, "root/dir1", cx); + + assert_eq!( + visible_entries_as_strings(&panel, 0..15, cx), + &[ + "v root", + " v dir1 <== selected <== marked", + " v subdir1 <== marked", + " a.txt", + " b.txt", + " file1.txt", + " v dir2", + " v subdir2", + " c.txt", + " d.txt", + " file2.txt", + " file3.txt", + ], + "Initial state before deleting nested directory with parent" + ); + + submit_deletion(&panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..15, cx), + &[ + "v root", + " v dir2 <== selected", + " v subdir2", + " c.txt", + " d.txt", + " file2.txt", + " file3.txt", + ], + "Should select next directory after deleting directory with parent" + ); + + // Test Case 2: Select mixed files and directories across levels + select_path_with_mark(&panel, "root/dir2/subdir2/c.txt", cx); + select_path_with_mark(&panel, "root/dir2/file2.txt", cx); + select_path_with_mark(&panel, "root/file3.txt", cx); + + assert_eq!( + visible_entries_as_strings(&panel, 0..15, cx), + &[ + "v root", + " v dir2", + " v subdir2", + " c.txt <== marked", + " d.txt", + " file2.txt <== marked", + " file3.txt <== selected <== marked", + ], + "Initial state before deleting" + ); + + submit_deletion(&panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..15, cx), + &[ + "v root", + " v dir2 <== selected", + " v subdir2", + " d.txt", + ], + "Should select sibling directory" + ); + } + + #[gpui::test] + async fn test_delete_all_files_and_directories(cx: &mut gpui::TestAppContext) { + init_test_with_editor(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/root", + json!({ + "dir1": { + "subdir1": { + "a.txt": "", + "b.txt": "" + }, + "file1.txt": "", + }, + "dir2": { + "subdir2": { + "c.txt": "", + "d.txt": "" + }, + "file2.txt": "", + }, + "file3.txt": "", + "file4.txt": "", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/root".as_ref()], cx).await; + let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let cx = &mut VisualTestContext::from_window(*workspace, cx); + let panel = workspace.update(cx, ProjectPanel::new).unwrap(); + + toggle_expand_dir(&panel, "root/dir1", cx); + toggle_expand_dir(&panel, "root/dir1/subdir1", cx); + toggle_expand_dir(&panel, "root/dir2", cx); + toggle_expand_dir(&panel, "root/dir2/subdir2", cx); + + // Test Case 1: Select all root files and directories + cx.simulate_modifiers_change(gpui::Modifiers { + control: true, + ..Default::default() + }); + select_path_with_mark(&panel, "root/dir1", cx); + select_path_with_mark(&panel, "root/dir2", cx); + select_path_with_mark(&panel, "root/file3.txt", cx); + select_path_with_mark(&panel, "root/file4.txt", cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &[ + "v root", + " v dir1 <== marked", + " v subdir1", + " a.txt", + " b.txt", + " file1.txt", + " v dir2 <== marked", + " v subdir2", + " c.txt", + " d.txt", + " file2.txt", + " file3.txt <== marked", + " file4.txt <== selected <== marked", + ], + "State before deleting all contents" + ); + + submit_deletion(&panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &["v root <== selected"], + "Only empty root directory should remain after deleting all contents" + ); + } + + #[gpui::test] + async fn test_nested_selection_deletion(cx: &mut gpui::TestAppContext) { + init_test_with_editor(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/root", + json!({ + "dir1": { + "subdir1": { + "file_a.txt": "content a", + "file_b.txt": "content b", + }, + "subdir2": { + "file_c.txt": "content c", + }, + "file1.txt": "content 1", + }, + "dir2": { + "file2.txt": "content 2", + }, + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/root".as_ref()], cx).await; + let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let cx = &mut VisualTestContext::from_window(*workspace, cx); + let panel = workspace.update(cx, ProjectPanel::new).unwrap(); + + toggle_expand_dir(&panel, "root/dir1", cx); + toggle_expand_dir(&panel, "root/dir1/subdir1", cx); + toggle_expand_dir(&panel, "root/dir2", cx); + cx.simulate_modifiers_change(gpui::Modifiers { + control: true, + ..Default::default() + }); + + // Test Case 1: Select parent directory, subdirectory, and a file inside the subdirectory + select_path_with_mark(&panel, "root/dir1", cx); + select_path_with_mark(&panel, "root/dir1/subdir1", cx); + select_path_with_mark(&panel, "root/dir1/subdir1/file_a.txt", cx); + + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &[ + "v root", + " v dir1 <== marked", + " v subdir1 <== marked", + " file_a.txt <== selected <== marked", + " file_b.txt", + " > subdir2", + " file1.txt", + " v dir2", + " file2.txt", + ], + "State with parent dir, subdir, and file selected" + ); + submit_deletion(&panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &["v root", " v dir2 <== selected", " file2.txt",], + "Only dir2 should remain after deletion" + ); + } + + #[gpui::test] + async fn test_multiple_worktrees_deletion(cx: &mut gpui::TestAppContext) { + init_test_with_editor(cx); + + let fs = FakeFs::new(cx.executor().clone()); + // First worktree + fs.insert_tree( + "/root1", + json!({ + "dir1": { + "file1.txt": "content 1", + "file2.txt": "content 2", + }, + "dir2": { + "file3.txt": "content 3", + }, + }), + ) + .await; + + // Second worktree + fs.insert_tree( + "/root2", + json!({ + "dir3": { + "file4.txt": "content 4", + "file5.txt": "content 5", + }, + "file6.txt": "content 6", + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; + let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let cx = &mut VisualTestContext::from_window(*workspace, cx); + let panel = workspace.update(cx, ProjectPanel::new).unwrap(); + + // Expand all directories for testing + toggle_expand_dir(&panel, "root1/dir1", cx); + toggle_expand_dir(&panel, "root1/dir2", cx); + toggle_expand_dir(&panel, "root2/dir3", cx); + + // Test Case 1: Delete files across different worktrees + cx.simulate_modifiers_change(gpui::Modifiers { + control: true, + ..Default::default() + }); + select_path_with_mark(&panel, "root1/dir1/file1.txt", cx); + select_path_with_mark(&panel, "root2/dir3/file4.txt", cx); + + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &[ + "v root1", + " v dir1", + " file1.txt <== marked", + " file2.txt", + " v dir2", + " file3.txt", + "v root2", + " v dir3", + " file4.txt <== selected <== marked", + " file5.txt", + " file6.txt", + ], + "Initial state with files selected from different worktrees" + ); + + submit_deletion(&panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &[ + "v root1", + " v dir1", + " file2.txt", + " v dir2", + " file3.txt", + "v root2", + " v dir3", + " file5.txt <== selected", + " file6.txt", + ], + "Should select next file in the last worktree after deletion" + ); + + // Test Case 2: Delete directories from different worktrees + select_path_with_mark(&panel, "root1/dir1", cx); + select_path_with_mark(&panel, "root2/dir3", cx); + + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &[ + "v root1", + " v dir1 <== marked", + " file2.txt", + " v dir2", + " file3.txt", + "v root2", + " v dir3 <== selected <== marked", + " file5.txt", + " file6.txt", + ], + "State with directories marked from different worktrees" + ); + + submit_deletion(&panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &[ + "v root1", + " v dir2", + " file3.txt", + "v root2", + " file6.txt <== selected", + ], + "Should select remaining file in last worktree after directory deletion" + ); + + // Test Case 4: Delete all remaining files except roots + select_path_with_mark(&panel, "root1/dir2/file3.txt", cx); + select_path_with_mark(&panel, "root2/file6.txt", cx); + + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &[ + "v root1", + " v dir2", + " file3.txt <== marked", + "v root2", + " file6.txt <== selected <== marked", + ], + "State with all remaining files marked" + ); + + submit_deletion(&panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &["v root1", " v dir2", "v root2 <== selected"], + "Second parent root should be selected after deleting" + ); + } + + #[gpui::test] + async fn test_selection_fallback_to_next_highest_worktree(cx: &mut gpui::TestAppContext) { + init_test_with_editor(cx); + + let fs = FakeFs::new(cx.executor().clone()); + fs.insert_tree( + "/root_b", + json!({ + "dir1": { + "file1.txt": "content 1", + "file2.txt": "content 2", + }, + }), + ) + .await; + + fs.insert_tree( + "/root_c", + json!({ + "dir2": {}, + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/root_b".as_ref(), "/root_c".as_ref()], cx).await; + let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let cx = &mut VisualTestContext::from_window(*workspace, cx); + let panel = workspace.update(cx, ProjectPanel::new).unwrap(); + + toggle_expand_dir(&panel, "root_b/dir1", cx); + toggle_expand_dir(&panel, "root_c/dir2", cx); + + cx.simulate_modifiers_change(gpui::Modifiers { + control: true, + ..Default::default() + }); + select_path_with_mark(&panel, "root_b/dir1/file1.txt", cx); + select_path_with_mark(&panel, "root_b/dir1/file2.txt", cx); + + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &[ + "v root_b", + " v dir1", + " file1.txt <== marked", + " file2.txt <== selected <== marked", + "v root_c", + " v dir2", + ], + "Initial state with files marked in root_b" + ); + + submit_deletion(&panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &[ + "v root_b", + " v dir1 <== selected", + "v root_c", + " v dir2", + ], + "After deletion in root_b as it's last deletion, selection should be in root_b" + ); + + select_path_with_mark(&panel, "root_c/dir2", cx); + + submit_deletion(&panel, cx); + assert_eq!( + visible_entries_as_strings(&panel, 0..20, cx), + &["v root_b", " v dir1", "v root_c <== selected",], + "After deleting from root_c, it should remain in root_c" + ); + } + fn toggle_expand_dir( panel: &View, path: impl AsRef, @@ -6364,6 +7100,32 @@ mod tests { }); } + fn select_path_with_mark( + panel: &View, + path: impl AsRef, + cx: &mut VisualTestContext, + ) { + let path = path.as_ref(); + panel.update(cx, |panel, cx| { + for worktree in panel.project.read(cx).worktrees(cx).collect::>() { + let worktree = worktree.read(cx); + if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) { + let entry_id = worktree.entry_for_path(relative_path).unwrap().id; + let entry = crate::SelectedEntry { + worktree_id: worktree.id(), + entry_id, + }; + if !panel.marked_entries.contains(&entry) { + panel.marked_entries.insert(entry); + } + panel.selection = Some(entry); + return; + } + } + panic!("no worktree for path {:?}", path); + }); + } + fn find_project_entry( panel: &View, path: impl AsRef, diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index d629c8facc..f4e494f66e 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -378,7 +378,15 @@ pub fn compare_paths( .as_deref() .map(NumericPrefixWithSuffix::from_numeric_prefixed_str); - num_and_remainder_a.cmp(&num_and_remainder_b) + num_and_remainder_a.cmp(&num_and_remainder_b).then_with(|| { + if a_is_file && b_is_file { + let ext_a = path_a.extension().unwrap_or_default(); + let ext_b = path_b.extension().unwrap_or_default(); + ext_a.cmp(ext_b) + } else { + cmp::Ordering::Equal + } + }) }); if !ordering.is_eq() { return ordering; @@ -433,6 +441,28 @@ mod tests { ); } + #[test] + fn compare_paths_with_same_name_different_extensions() { + let mut paths = vec![ + (Path::new("test_dirs/file.rs"), true), + (Path::new("test_dirs/file.txt"), true), + (Path::new("test_dirs/file.md"), true), + (Path::new("test_dirs/file"), true), + (Path::new("test_dirs/file.a"), true), + ]; + paths.sort_by(|&a, &b| compare_paths(a, b)); + assert_eq!( + paths, + vec![ + (Path::new("test_dirs/file"), true), + (Path::new("test_dirs/file.a"), true), + (Path::new("test_dirs/file.md"), true), + (Path::new("test_dirs/file.rs"), true), + (Path::new("test_dirs/file.txt"), true), + ] + ); + } + #[test] fn compare_paths_case_semi_sensitive() { let mut paths = vec![ diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index e9b81d4554..4eec2f18d1 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -48,7 +48,7 @@ use ui::{v_flex, ContextMenu}; use util::{debug_panic, maybe, truncate_and_remove_front, ResultExt}; /// A selected entry in e.g. project panel. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SelectedEntry { pub worktree_id: WorktreeId, pub entry_id: ProjectEntryId, From d5f2bca382e5b5d653f917ec9d52fdb028acee2b Mon Sep 17 00:00:00 2001 From: Techatrix Date: Fri, 22 Nov 2024 13:01:00 +0100 Subject: [PATCH 123/157] Filter LSP code actions based on the requested kinds (#20847) I've observed that Zed's implementation of [Code Actions On Format](https://zed.dev/docs/configuring-zed#code-actions-on-format) uses the [CodeActionContext.only](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionContext) parameter to request specific code action kinds from the server. The issue is that it does not filter out code actions from the response, believing that the server will do it. The [LSP specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionContext) says that the client is responsible for filtering out unwanted code actions: ```js /** * Requested kind of actions to return. * * Actions not of this kind are filtered out by the client before being * shown. So servers can omit computing them. */ only?: CodeActionKind[]; ``` This PR will filter out unwanted code action on the client side. I have initially encountered this issue because the [ZLS language server](https://github.com/zigtools/zls) (until https://github.com/zigtools/zls/pull/2087) does not filter code action based on `CodeActionContext.only` so Zed runs all received code actions even if they are explicitly disabled in the `code_actions_on_format` setting. Release Notes: - Fix the `code_actions_on_format` setting when used with a language server like ZLS --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> --- .../random_project_collaboration_tests.rs | 2 +- crates/editor/src/editor.rs | 4 +- crates/project/src/lsp_command.rs | 30 +++++-- crates/project/src/lsp_store.rs | 7 +- crates/project/src/project.rs | 7 +- crates/project/src/project_tests.rs | 84 ++++++++++++++++++- 6 files changed, 116 insertions(+), 18 deletions(-) diff --git a/crates/collab/src/tests/random_project_collaboration_tests.rs b/crates/collab/src/tests/random_project_collaboration_tests.rs index 66a9d06804..1f39190d75 100644 --- a/crates/collab/src/tests/random_project_collaboration_tests.rs +++ b/crates/collab/src/tests/random_project_collaboration_tests.rs @@ -835,7 +835,7 @@ impl RandomizedTest for ProjectCollaborationTest { .map_ok(|_| ()) .boxed(), LspRequestKind::CodeAction => project - .code_actions(&buffer, offset..offset, cx) + .code_actions(&buffer, offset..offset, None, cx) .map(|_| Ok(())) .boxed(), LspRequestKind::Definition => project diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b31938bcfd..401462795e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -13811,7 +13811,9 @@ impl CodeActionProvider for Model { range: Range, cx: &mut WindowContext, ) -> Task>> { - self.update(cx, |project, cx| project.code_actions(buffer, range, cx)) + self.update(cx, |project, cx| { + project.code_actions(buffer, range, None, cx) + }) } fn apply_code_action( diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 57f8cea348..6de4902746 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -2090,19 +2090,33 @@ impl LspCommand for GetCodeActions { server_id: LanguageServerId, _: AsyncAppContext, ) -> Result> { + let requested_kinds_set = if let Some(kinds) = self.kinds { + Some(kinds.into_iter().collect::>()) + } else { + None + }; + Ok(actions .unwrap_or_default() .into_iter() .filter_map(|entry| { - if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry { - Some(CodeAction { - server_id, - range: self.range.clone(), - lsp_action, - }) - } else { - None + let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry else { + return None; + }; + + if let Some((requested_kinds, kind)) = + requested_kinds_set.as_ref().zip(lsp_action.kind.as_ref()) + { + if !requested_kinds.contains(kind) { + return None; + } } + + Some(CodeAction { + server_id, + range: self.range.clone(), + lsp_action, + }) }) .collect()) } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 3ed311a51d..29a4c8e71b 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -2015,6 +2015,7 @@ impl LspStore { &mut self, buffer_handle: &Model, range: Range, + kinds: Option>, cx: &mut ModelContext, ) -> Task>> { if let Some((upstream_client, project_id)) = self.upstream_client() { @@ -2028,7 +2029,7 @@ impl LspStore { request: Some(proto::multi_lsp_query::Request::GetCodeActions( GetCodeActions { range: range.clone(), - kinds: None, + kinds: kinds.clone(), } .to_proto(project_id, buffer_handle.read(cx)), )), @@ -2054,7 +2055,7 @@ impl LspStore { .map(|code_actions_response| { GetCodeActions { range: range.clone(), - kinds: None, + kinds: kinds.clone(), } .response_from_proto( code_actions_response, @@ -2079,7 +2080,7 @@ impl LspStore { Some(range.start), GetCodeActions { range: range.clone(), - kinds: None, + kinds: kinds.clone(), }, cx, ); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 61a700e5d6..40da76ff3a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -52,8 +52,8 @@ use language::{ Transaction, Unclipped, }; use lsp::{ - CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServer, LanguageServerId, - LanguageServerName, MessageActionItem, + CodeActionKind, CompletionContext, CompletionItemKind, DocumentHighlightKind, LanguageServer, + LanguageServerId, LanguageServerName, MessageActionItem, }; use lsp_command::*; use node_runtime::NodeRuntime; @@ -2843,12 +2843,13 @@ impl Project { &mut self, buffer_handle: &Model, range: Range, + kinds: Option>, cx: &mut ModelContext, ) -> Task>> { let buffer = buffer_handle.read(cx); let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end); self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.code_actions(buffer_handle, range, cx) + lsp_store.code_actions(buffer_handle, range, kinds, cx) }) } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index ab00d62d6c..2704259306 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -2792,7 +2792,9 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { let fake_server = fake_language_servers.next().await.unwrap(); // Language server returns code actions that contain commands, and not edits. - let actions = project.update(cx, |project, cx| project.code_actions(&buffer, 0..0, cx)); + let actions = project.update(cx, |project, cx| { + project.code_actions(&buffer, 0..0, None, cx) + }); fake_server .handle_request::(|_, _| async move { Ok(Some(vec![ @@ -4961,6 +4963,84 @@ async fn test_hovers_with_empty_parts(cx: &mut gpui::TestAppContext) { ); } +#[gpui::test] +async fn test_code_actions_only_kinds(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + "/dir", + json!({ + "a.ts": "a", + }), + ) + .await; + + let project = Project::test(fs, ["/dir".as_ref()], cx).await; + + let language_registry = project.read_with(cx, |project, _| project.languages().clone()); + language_registry.add(typescript_lang()); + let mut fake_language_servers = language_registry.register_fake_lsp( + "TypeScript", + FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)), + ..lsp::ServerCapabilities::default() + }, + ..FakeLspAdapter::default() + }, + ); + + let buffer = project + .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) + .await + .unwrap(); + cx.executor().run_until_parked(); + + let fake_server = fake_language_servers + .next() + .await + .expect("failed to get the language server"); + + let mut request_handled = fake_server.handle_request::( + move |_, _| async move { + Ok(Some(vec![ + lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction { + title: "organize imports".to_string(), + kind: Some(CodeActionKind::SOURCE_ORGANIZE_IMPORTS), + ..lsp::CodeAction::default() + }), + lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction { + title: "fix code".to_string(), + kind: Some(CodeActionKind::SOURCE_FIX_ALL), + ..lsp::CodeAction::default() + }), + ])) + }, + ); + + let code_actions_task = project.update(cx, |project, cx| { + project.code_actions( + &buffer, + 0..buffer.read(cx).len(), + Some(vec![CodeActionKind::SOURCE_ORGANIZE_IMPORTS]), + cx, + ) + }); + + let () = request_handled + .next() + .await + .expect("The code action request should have been triggered"); + + let code_actions = code_actions_task.await.unwrap(); + assert_eq!(code_actions.len(), 1); + assert_eq!( + code_actions[0].lsp_action.kind, + Some(CodeActionKind::SOURCE_ORGANIZE_IMPORTS) + ); +} + #[gpui::test] async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) { init_test(cx); @@ -5092,7 +5172,7 @@ async fn test_multiple_language_server_actions(cx: &mut gpui::TestAppContext) { } let code_actions_task = project.update(cx, |project, cx| { - project.code_actions(&buffer, 0..buffer.read(cx).len(), cx) + project.code_actions(&buffer, 0..buffer.read(cx).len(), None, cx) }); // cx.run_until_parked(); From b4659bb44ed148f11a0874159fbb58538052dc96 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Fri, 22 Nov 2024 15:10:01 +0000 Subject: [PATCH 124/157] Fix inaccurate Ollama context length for qwen2.5 models (#20933) Since Ollama/llama.cpp do not currently YARN for context length extension, the context length is limited to `32768`. This can be confirmed by the Ollama model card. See corresponding issue on Ollama repo : https://github.com/ollama/ollama/issues/6865 Co-authored-by: Patrick Samson <1416027+patricksamson@users.noreply.github.com> --- crates/ollama/src/ollama.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/ollama/src/ollama.rs b/crates/ollama/src/ollama.rs index a133085020..5168da38be 100644 --- a/crates/ollama/src/ollama.rs +++ b/crates/ollama/src/ollama.rs @@ -81,9 +81,10 @@ fn get_max_tokens(name: &str) -> usize { "llama2" | "yi" | "vicuna" | "stablelm2" => 4096, "llama3" | "gemma2" | "gemma" | "codegemma" | "starcoder" | "aya" => 8192, "codellama" | "starcoder2" => 16384, - "mistral" | "codestral" | "mixstral" | "llava" | "qwen2" | "dolphin-mixtral" => 32768, + "mistral" | "codestral" | "mixstral" | "llava" | "qwen2" | "qwen2.5-coder" + | "dolphin-mixtral" => 32768, "llama3.1" | "phi3" | "phi3.5" | "command-r" | "deepseek-coder-v2" | "yi-coder" - | "llama3.2" | "qwen2.5-coder" => 128000, + | "llama3.2" => 128000, _ => DEFAULT_TOKENS, } .clamp(1, MAXIMUM_TOKENS) From d489f96aefb6166f42fcdd9cd40f1c765244b66d Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 22 Nov 2024 10:58:11 -0500 Subject: [PATCH 125/157] Don't name `ExtensionLspAdapter` in `ExtensionRegistrationHooks` (#21064) This PR updates the `ExtensionRegistrationHooks` trait to not name the `ExtensionLspAdapter` type. This helps decouple the two. Release Notes: - N/A --- crates/extension_host/src/extension_host.rs | 16 +++++----- .../src/extension_lsp_adapter.rs | 20 +++++++++++-- .../src/extension_store_test.rs | 20 +++++++++---- crates/extension_host/src/headless_host.rs | 29 ++++++++++++------- .../src/extension_registration_hooks.rs | 18 ++++++++---- 5 files changed, 73 insertions(+), 30 deletions(-) diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 4a832faeff..236c8091b4 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -6,7 +6,6 @@ pub mod wasm_host; #[cfg(test)] mod extension_store_test; -use crate::extension_lsp_adapter::ExtensionLspAdapter; use anyhow::{anyhow, bail, Context as _, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; @@ -122,7 +121,13 @@ pub trait ExtensionRegistrationHooks: Send + Sync + 'static { ) { } - fn register_lsp_adapter(&self, _language: LanguageName, _adapter: ExtensionLspAdapter) {} + fn register_lsp_adapter( + &self, + _extension: Arc, + _language_server_id: LanguageServerName, + _language: LanguageName, + ) { + } fn remove_lsp_adapter(&self, _language: &LanguageName, _server_name: &LanguageServerName) {} @@ -1255,12 +1260,9 @@ impl ExtensionStore { for (language_server_id, language_server_config) in &manifest.language_servers { for language in language_server_config.languages() { this.registration_hooks.register_lsp_adapter( + extension.clone(), + language_server_id.clone(), language.clone(), - ExtensionLspAdapter { - extension: extension.clone(), - language_server_id: language_server_id.clone(), - language_name: language.clone(), - }, ); } } diff --git a/crates/extension_host/src/extension_lsp_adapter.rs b/crates/extension_host/src/extension_lsp_adapter.rs index 8f83c68e31..069eddba57 100644 --- a/crates/extension_host/src/extension_lsp_adapter.rs +++ b/crates/extension_host/src/extension_lsp_adapter.rs @@ -45,9 +45,23 @@ impl WorktreeDelegate for WorktreeDelegateAdapter { } pub struct ExtensionLspAdapter { - pub(crate) extension: Arc, - pub(crate) language_server_id: LanguageServerName, - pub(crate) language_name: LanguageName, + extension: Arc, + language_server_id: LanguageServerName, + language_name: LanguageName, +} + +impl ExtensionLspAdapter { + pub fn new( + extension: Arc, + language_server_id: LanguageServerName, + language_name: LanguageName, + ) -> Self { + Self { + extension, + language_server_id, + language_name, + } + } } #[async_trait(?Send)] diff --git a/crates/extension_host/src/extension_store_test.rs b/crates/extension_host/src/extension_store_test.rs index 23004e9d7f..5d78539617 100644 --- a/crates/extension_host/src/extension_store_test.rs +++ b/crates/extension_host/src/extension_store_test.rs @@ -7,11 +7,14 @@ use crate::{ use anyhow::Result; use async_compression::futures::bufread::GzipEncoder; use collections::BTreeMap; +use extension::Extension; use fs::{FakeFs, Fs, RealFs}; use futures::{io::BufReader, AsyncReadExt, StreamExt}; use gpui::{BackgroundExecutor, Context, SemanticVersion, SharedString, Task, TestAppContext}; use http_client::{FakeHttpClient, Response}; -use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage}; +use language::{ + LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage, +}; use lsp::LanguageServerName; use node_runtime::NodeRuntime; use parking_lot::Mutex; @@ -80,11 +83,18 @@ impl ExtensionRegistrationHooks for TestExtensionRegistrationHooks { fn register_lsp_adapter( &self, - language_name: language::LanguageName, - adapter: ExtensionLspAdapter, + extension: Arc, + language_server_id: LanguageServerName, + language: LanguageName, ) { - self.language_registry - .register_lsp_adapter(language_name, Arc::new(adapter)); + self.language_registry.register_lsp_adapter( + language.clone(), + Arc::new(ExtensionLspAdapter::new( + extension, + language_server_id, + language, + )), + ); } fn update_lsp_status( diff --git a/crates/extension_host/src/headless_host.rs b/crates/extension_host/src/headless_host.rs index e297794bf1..6ad8b71aa3 100644 --- a/crates/extension_host/src/headless_host.rs +++ b/crates/extension_host/src/headless_host.rs @@ -177,20 +177,17 @@ impl HeadlessExtensionStore { let wasm_extension: Arc = Arc::new(WasmExtension::load(extension_dir, &manifest, wasm_host.clone(), &cx).await?); - for (language_server_name, language_server_config) in &manifest.language_servers { + for (language_server_id, language_server_config) in &manifest.language_servers { for language in language_server_config.languages() { this.update(cx, |this, _cx| { this.loaded_language_servers .entry(manifest.id.clone()) .or_default() - .push((language_server_name.clone(), language.clone())); + .push((language_server_id.clone(), language.clone())); this.registration_hooks.register_lsp_adapter( + wasm_extension.clone(), + language_server_id.clone(), language.clone(), - ExtensionLspAdapter { - extension: wasm_extension.clone(), - language_server_id: language_server_name.clone(), - language_name: language, - }, ); })?; } @@ -344,10 +341,22 @@ impl ExtensionRegistrationHooks for HeadlessRegistrationHooks { self.language_registry .register_language(language, None, matcher, load) } - fn register_lsp_adapter(&self, language: LanguageName, adapter: ExtensionLspAdapter) { + + fn register_lsp_adapter( + &self, + extension: Arc, + language_server_id: LanguageServerName, + language: LanguageName, + ) { log::info!("registering lsp adapter {:?}", language); - self.language_registry - .register_lsp_adapter(language, Arc::new(adapter) as _); + self.language_registry.register_lsp_adapter( + language.clone(), + Arc::new(ExtensionLspAdapter::new( + extension, + language_server_id, + language, + )), + ); } fn register_wasm_grammars(&self, grammars: Vec<(Arc, PathBuf)>) { diff --git a/crates/extensions_ui/src/extension_registration_hooks.rs b/crates/extensions_ui/src/extension_registration_hooks.rs index f8cd9a3429..07a4c1455c 100644 --- a/crates/extensions_ui/src/extension_registration_hooks.rs +++ b/crates/extensions_ui/src/extension_registration_hooks.rs @@ -11,7 +11,8 @@ use extension_host::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host}; use fs::Fs; use gpui::{AppContext, BackgroundExecutor, Model, Task}; use indexed_docs::{ExtensionIndexedDocsProvider, IndexedDocsRegistry, ProviderId}; -use language::{LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage}; +use language::{LanguageName, LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage}; +use lsp::LanguageServerName; use snippet_provider::SnippetRegistry; use theme::{ThemeRegistry, ThemeSettings}; use ui::SharedString; @@ -159,11 +160,18 @@ impl extension_host::ExtensionRegistrationHooks for ConcreteExtensionRegistratio fn register_lsp_adapter( &self, - language_name: language::LanguageName, - adapter: ExtensionLspAdapter, + extension: Arc, + language_server_id: LanguageServerName, + language: LanguageName, ) { - self.language_registry - .register_lsp_adapter(language_name, Arc::new(adapter)); + self.language_registry.register_lsp_adapter( + language.clone(), + Arc::new(ExtensionLspAdapter::new( + extension, + language_server_id, + language, + )), + ); } fn remove_lsp_adapter( From 852fb5152869f31a9bc96bdd7459135f2ea2d1cd Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 22 Nov 2024 09:20:49 -0700 Subject: [PATCH 126/157] Canonicalize paths when opening workspaces (#21039) Closes #17161 Release Notes: - Added symlink resolution when opening projects from within Zed. Previously this only happened within zed's cli, but that broke file watching on Linux when opening a symlinked directory. --- crates/workspace/src/workspace.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 32e441ee50..45de781577 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1096,10 +1096,17 @@ impl Workspace { ); cx.spawn(|mut cx| async move { - let serialized_workspace: Option = - persistence::DB.workspace_for_roots(abs_paths.as_slice()); + let mut paths_to_open = Vec::with_capacity(abs_paths.len()); + for path in abs_paths.into_iter() { + if let Some(canonical) = app_state.fs.canonicalize(&path).await.ok() { + paths_to_open.push(canonical) + } else { + paths_to_open.push(path) + } + } - let mut paths_to_open = abs_paths; + let serialized_workspace: Option = + persistence::DB.workspace_for_roots(paths_to_open.as_slice()); let workspace_location = serialized_workspace .as_ref() From ca769480443caf7137466713b15dbda442dadc07 Mon Sep 17 00:00:00 2001 From: william341 Date: Fri, 22 Nov 2024 10:51:26 -0700 Subject: [PATCH 127/157] gpui: Add drop_image (#19772) This PR adds a function, WindowContext::drop_image, to manually remove a RenderImage from the sprite atlas. In addition, PlatformAtlas::remove was added to support this behavior. Previously, there was no way to request a RenderImage to be removed from the sprite atlas, and since they are not removed automatically the sprite would remain in video memory once added until the window was closed. This PR allows a developer to request the image be dropped from memory manually, however it does not add automatic removal. Release Notes: - N/A --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Co-authored-by: Mikayla Maki --- crates/gpui/src/platform.rs | 37 +++++++ crates/gpui/src/platform/blade/blade_atlas.rs | 96 ++++++++++++---- crates/gpui/src/platform/mac/metal_atlas.rs | 104 ++++++++++++++---- crates/gpui/src/platform/test/window.rs | 5 + crates/gpui/src/window.rs | 14 +++ 5 files changed, 208 insertions(+), 48 deletions(-) diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 76a575724f..8228d44bb4 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -46,6 +46,7 @@ use smallvec::SmallVec; use std::borrow::Cow; use std::hash::{Hash, Hasher}; use std::io::Cursor; +use std::ops; use std::time::{Duration, Instant}; use std::{ fmt::{self, Debug}, @@ -561,6 +562,42 @@ pub(crate) trait PlatformAtlas: Send + Sync { key: &AtlasKey, build: &mut dyn FnMut() -> Result, Cow<'a, [u8]>)>>, ) -> Result>; + fn remove(&self, key: &AtlasKey); +} + +struct AtlasTextureList { + textures: Vec>, + free_list: Vec, +} + +impl Default for AtlasTextureList { + fn default() -> Self { + Self { + textures: Vec::default(), + free_list: Vec::default(), + } + } +} + +impl ops::Index for AtlasTextureList { + type Output = Option; + + fn index(&self, index: usize) -> &Self::Output { + &self.textures[index] + } +} + +impl AtlasTextureList { + #[allow(unused)] + fn drain(&mut self) -> std::vec::Drain> { + self.free_list.clear(); + self.textures.drain(..) + } + + #[allow(dead_code)] + fn iter_mut(&mut self) -> impl DoubleEndedIterator { + self.textures.iter_mut().flatten() + } } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/gpui/src/platform/blade/blade_atlas.rs b/crates/gpui/src/platform/blade/blade_atlas.rs index e6d5dc8ee9..b876d5bb9b 100644 --- a/crates/gpui/src/platform/blade/blade_atlas.rs +++ b/crates/gpui/src/platform/blade/blade_atlas.rs @@ -1,6 +1,6 @@ use crate::{ - AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas, - Point, Size, + platform::AtlasTextureList, AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, + DevicePixels, PlatformAtlas, Point, Size, }; use anyhow::Result; use blade_graphics as gpu; @@ -67,7 +67,7 @@ impl BladeAtlas { pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) { let mut lock = self.0.lock(); let textures = &mut lock.storage[texture_kind]; - for texture in textures { + for texture in textures.iter_mut() { texture.clear(); } } @@ -130,19 +130,48 @@ impl PlatformAtlas for BladeAtlas { Ok(Some(tile)) } } + + fn remove(&self, key: &AtlasKey) { + let mut lock = self.0.lock(); + + let Some(id) = lock.tiles_by_key.remove(key).map(|tile| tile.texture_id) else { + return; + }; + + let Some(texture_slot) = lock.storage[id.kind].textures.get_mut(id.index as usize) else { + return; + }; + + if let Some(mut texture) = texture_slot.take() { + texture.decrement_ref_count(); + if texture.is_unreferenced() { + lock.storage[id.kind] + .free_list + .push(texture.id.index as usize); + texture.destroy(&lock.gpu); + } else { + *texture_slot = Some(texture); + } + } + } } impl BladeAtlasState { fn allocate(&mut self, size: Size, texture_kind: AtlasTextureKind) -> AtlasTile { - let textures = &mut self.storage[texture_kind]; - textures - .iter_mut() - .rev() - .find_map(|texture| texture.allocate(size)) - .unwrap_or_else(|| { - let texture = self.push_texture(size, texture_kind); - texture.allocate(size).unwrap() - }) + { + let textures = &mut self.storage[texture_kind]; + + if let Some(tile) = textures + .iter_mut() + .rev() + .find_map(|texture| texture.allocate(size)) + { + return tile; + } + } + + let texture = self.push_texture(size, texture_kind); + texture.allocate(size).unwrap() } fn push_texture( @@ -198,21 +227,30 @@ impl BladeAtlasState { }, ); - let textures = &mut self.storage[kind]; + let texture_list = &mut self.storage[kind]; + let index = texture_list.free_list.pop(); + let atlas_texture = BladeAtlasTexture { id: AtlasTextureId { - index: textures.len() as u32, + index: index.unwrap_or(texture_list.textures.len()) as u32, kind, }, allocator: etagere::BucketedAtlasAllocator::new(size.into()), format, raw, raw_view, + live_atlas_keys: 0, }; self.initializations.push(atlas_texture.id); - textures.push(atlas_texture); - textures.last_mut().unwrap() + + if let Some(ix) = index { + texture_list.textures[ix] = Some(atlas_texture); + texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap() + } else { + texture_list.textures.push(Some(atlas_texture)); + texture_list.textures.last_mut().unwrap().as_mut().unwrap() + } } fn upload_texture(&mut self, id: AtlasTextureId, bounds: Bounds, bytes: &[u8]) { @@ -258,13 +296,13 @@ impl BladeAtlasState { #[derive(Default)] struct BladeAtlasStorage { - monochrome_textures: Vec, - polychrome_textures: Vec, - path_textures: Vec, + monochrome_textures: AtlasTextureList, + polychrome_textures: AtlasTextureList, + path_textures: AtlasTextureList, } impl ops::Index for BladeAtlasStorage { - type Output = Vec; + type Output = AtlasTextureList; fn index(&self, kind: AtlasTextureKind) -> &Self::Output { match kind { crate::AtlasTextureKind::Monochrome => &self.monochrome_textures, @@ -292,19 +330,19 @@ impl ops::Index for BladeAtlasStorage { crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, crate::AtlasTextureKind::Path => &self.path_textures, }; - &textures[id.index as usize] + textures[id.index as usize].as_ref().unwrap() } } impl BladeAtlasStorage { fn destroy(&mut self, gpu: &gpu::Context) { - for mut texture in self.monochrome_textures.drain(..) { + for mut texture in self.monochrome_textures.drain().flatten() { texture.destroy(gpu); } - for mut texture in self.polychrome_textures.drain(..) { + for mut texture in self.polychrome_textures.drain().flatten() { texture.destroy(gpu); } - for mut texture in self.path_textures.drain(..) { + for mut texture in self.path_textures.drain().flatten() { texture.destroy(gpu); } } @@ -316,6 +354,7 @@ struct BladeAtlasTexture { raw: gpu::Texture, raw_view: gpu::TextureView, format: gpu::TextureFormat, + live_atlas_keys: u32, } impl BladeAtlasTexture { @@ -334,6 +373,7 @@ impl BladeAtlasTexture { size, }, }; + self.live_atlas_keys += 1; Some(tile) } @@ -345,6 +385,14 @@ impl BladeAtlasTexture { fn bytes_per_pixel(&self) -> u8 { self.format.block_info().size } + + fn decrement_ref_count(&mut self) { + self.live_atlas_keys -= 1; + } + + fn is_unreferenced(&mut self) -> bool { + self.live_atlas_keys == 0 + } } impl From> for etagere::Size { diff --git a/crates/gpui/src/platform/mac/metal_atlas.rs b/crates/gpui/src/platform/mac/metal_atlas.rs index 89a6987752..ca595c5ce3 100644 --- a/crates/gpui/src/platform/mac/metal_atlas.rs +++ b/crates/gpui/src/platform/mac/metal_atlas.rs @@ -1,6 +1,6 @@ use crate::{ - AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas, - Point, Size, + platform::AtlasTextureList, AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, + DevicePixels, PlatformAtlas, Point, Size, }; use anyhow::{anyhow, Result}; use collections::FxHashMap; @@ -42,7 +42,7 @@ impl MetalAtlas { AtlasTextureKind::Polychrome => &mut lock.polychrome_textures, AtlasTextureKind::Path => &mut lock.path_textures, }; - for texture in textures { + for texture in textures.iter_mut() { texture.clear(); } } @@ -50,9 +50,9 @@ impl MetalAtlas { struct MetalAtlasState { device: AssertSend, - monochrome_textures: Vec, - polychrome_textures: Vec, - path_textures: Vec, + monochrome_textures: AtlasTextureList, + polychrome_textures: AtlasTextureList, + path_textures: AtlasTextureList, tiles_by_key: FxHashMap, } @@ -78,6 +78,38 @@ impl PlatformAtlas for MetalAtlas { Ok(Some(tile)) } } + + fn remove(&self, key: &AtlasKey) { + let mut lock = self.0.lock(); + let Some(id) = lock.tiles_by_key.get(key).map(|v| v.texture_id) else { + return; + }; + + let textures = match id.kind { + AtlasTextureKind::Monochrome => &mut lock.monochrome_textures, + AtlasTextureKind::Polychrome => &mut lock.polychrome_textures, + AtlasTextureKind::Path => &mut lock.polychrome_textures, + }; + + let Some(texture_slot) = textures + .textures + .iter_mut() + .find(|texture| texture.as_ref().is_some_and(|v| v.id == id)) + else { + return; + }; + + if let Some(mut texture) = texture_slot.take() { + texture.decrement_ref_count(); + + if texture.is_unreferenced() { + textures.free_list.push(id.index as usize); + lock.tiles_by_key.remove(key); + } else { + *texture_slot = Some(texture); + } + } + } } impl MetalAtlasState { @@ -86,20 +118,24 @@ impl MetalAtlasState { size: Size, texture_kind: AtlasTextureKind, ) -> Option { - let textures = match texture_kind { - AtlasTextureKind::Monochrome => &mut self.monochrome_textures, - AtlasTextureKind::Polychrome => &mut self.polychrome_textures, - AtlasTextureKind::Path => &mut self.path_textures, - }; + { + let textures = match texture_kind { + AtlasTextureKind::Monochrome => &mut self.monochrome_textures, + AtlasTextureKind::Polychrome => &mut self.polychrome_textures, + AtlasTextureKind::Path => &mut self.path_textures, + }; - textures - .iter_mut() - .rev() - .find_map(|texture| texture.allocate(size)) - .or_else(|| { - let texture = self.push_texture(size, texture_kind); - texture.allocate(size) - }) + if let Some(tile) = textures + .iter_mut() + .rev() + .find_map(|texture| texture.allocate(size)) + { + return Some(tile); + } + } + + let texture = self.push_texture(size, texture_kind); + texture.allocate(size) } fn push_texture( @@ -140,21 +176,31 @@ impl MetalAtlasState { texture_descriptor.set_usage(usage); let metal_texture = self.device.new_texture(&texture_descriptor); - let textures = match kind { + let texture_list = match kind { AtlasTextureKind::Monochrome => &mut self.monochrome_textures, AtlasTextureKind::Polychrome => &mut self.polychrome_textures, AtlasTextureKind::Path => &mut self.path_textures, }; + + let index = texture_list.free_list.pop(); + let atlas_texture = MetalAtlasTexture { id: AtlasTextureId { - index: textures.len() as u32, + index: index.unwrap_or(texture_list.textures.len()) as u32, kind, }, allocator: etagere::BucketedAtlasAllocator::new(size.into()), metal_texture: AssertSend(metal_texture), + live_atlas_keys: 0, }; - textures.push(atlas_texture); - textures.last_mut().unwrap() + + if let Some(ix) = index { + texture_list.textures[ix] = Some(atlas_texture); + texture_list.textures.get_mut(ix).unwrap().as_mut().unwrap() + } else { + texture_list.textures.push(Some(atlas_texture)); + texture_list.textures.last_mut().unwrap().as_mut().unwrap() + } } fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture { @@ -163,7 +209,7 @@ impl MetalAtlasState { crate::AtlasTextureKind::Polychrome => &self.polychrome_textures, crate::AtlasTextureKind::Path => &self.path_textures, }; - &textures[id.index as usize] + textures[id.index as usize].as_ref().unwrap() } } @@ -171,6 +217,7 @@ struct MetalAtlasTexture { id: AtlasTextureId, allocator: BucketedAtlasAllocator, metal_texture: AssertSend, + live_atlas_keys: u32, } impl MetalAtlasTexture { @@ -189,6 +236,7 @@ impl MetalAtlasTexture { }, padding: 0, }; + self.live_atlas_keys += 1; Some(tile) } @@ -215,6 +263,14 @@ impl MetalAtlasTexture { _ => unimplemented!(), } } + + fn decrement_ref_count(&mut self) { + self.live_atlas_keys -= 1; + } + + fn is_unreferenced(&mut self) -> bool { + self.live_atlas_keys == 0 + } } impl From> for etagere::Size { diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index d8ec6a718b..9c94aeaf2f 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -339,4 +339,9 @@ impl PlatformAtlas for TestAtlas { Ok(Some(state.tiles[key].clone())) } + + fn remove(&self, key: &AtlasKey) { + let mut state = self.0.lock(); + state.tiles.remove(key); + } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index e4fa74f981..2b6f1d4a99 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2685,6 +2685,20 @@ impl<'a> WindowContext<'a> { }); } + /// Removes an image from the sprite atlas. + pub fn drop_image(&mut self, data: Arc) -> Result<()> { + for frame_index in 0..data.frame_count() { + let params = RenderImageParams { + image_id: data.id, + frame_index, + }; + + self.window.sprite_atlas.remove(¶ms.clone().into()); + } + + Ok(()) + } + #[must_use] /// Add a node to the layout tree for the current frame. Takes the `Style` of the element for which /// layout is being requested, along with the layout ids of any children. This method is called during From cb8028c0928bb2f0a609201b416565255c2d2453 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 22 Nov 2024 13:21:30 -0500 Subject: [PATCH 128/157] Use `Extension` trait when registering extension context servers (#21070) This PR updates the extension context server registration to go through the `Extension` trait for interacting with extensions rather than going through the `WasmHost` directly. Release Notes: - N/A --- Cargo.lock | 1 - crates/extension/src/extension.rs | 10 ++++ crates/extension/src/types.rs | 1 + crates/extension_host/src/extension_host.rs | 4 +- crates/extension_host/src/wasm_host.rs | 22 ++++++- .../src/wasm_host/wit/since_v0_2_0.rs | 9 +-- crates/extensions_ui/Cargo.toml | 1 - .../src/extension_registration_hooks.rs | 57 ++++++++----------- 8 files changed, 59 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75d69fdcf9..514338c590 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4237,7 +4237,6 @@ dependencies = [ "ui", "util", "vim_mode_setting", - "wasmtime-wasi", "workspace", "zed_actions", ] diff --git a/crates/extension/src/extension.rs b/crates/extension/src/extension.rs index a3c275c537..fe9b49909b 100644 --- a/crates/extension/src/extension.rs +++ b/crates/extension/src/extension.rs @@ -25,6 +25,10 @@ pub trait WorktreeDelegate: Send + Sync + 'static { async fn shell_env(&self) -> Vec<(String, String)>; } +pub trait ProjectDelegate: Send + Sync + 'static { + fn worktree_ids(&self) -> Vec; +} + pub trait KeyValueStoreDelegate: Send + Sync + 'static { fn insert(&self, key: String, docs: String) -> Task>; } @@ -87,6 +91,12 @@ pub trait Extension: Send + Sync + 'static { worktree: Option>, ) -> Result; + async fn context_server_command( + &self, + context_server_id: Arc, + project: Arc, + ) -> Result; + async fn suggest_docs_packages(&self, provider: Arc) -> Result>; async fn index_docs( diff --git a/crates/extension/src/types.rs b/crates/extension/src/types.rs index f4c37b5daf..f04d31300f 100644 --- a/crates/extension/src/types.rs +++ b/crates/extension/src/types.rs @@ -10,6 +10,7 @@ pub use slash_command::*; pub type EnvVars = Vec<(String, String)>; /// A command. +#[derive(Debug)] pub struct Command { /// The command to execute. pub command: String, diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 236c8091b4..85da812795 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -149,8 +149,8 @@ pub trait ExtensionRegistrationHooks: Send + Sync + 'static { fn register_context_server( &self, + _extension: Arc, _id: Arc, - _extension: WasmExtension, _cx: &mut AppContext, ) { } @@ -1284,8 +1284,8 @@ impl ExtensionStore { for (id, _context_server_entry) in &manifest.context_servers { this.registration_hooks.register_context_server( + extension.clone(), id.clone(), - wasm_extension.clone(), cx, ); } diff --git a/crates/extension_host/src/wasm_host.rs b/crates/extension_host/src/wasm_host.rs index 54699ac0a1..01c57599a8 100644 --- a/crates/extension_host/src/wasm_host.rs +++ b/crates/extension_host/src/wasm_host.rs @@ -4,7 +4,7 @@ use crate::{ExtensionManifest, ExtensionRegistrationHooks}; use anyhow::{anyhow, bail, Context as _, Result}; use async_trait::async_trait; use extension::{ - CodeLabel, Command, Completion, KeyValueStoreDelegate, SlashCommand, + CodeLabel, Command, Completion, KeyValueStoreDelegate, ProjectDelegate, SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate, }; use fs::{normalize_path, Fs}; @@ -34,7 +34,6 @@ use wasmtime::{ }; use wasmtime_wasi::{self as wasi, WasiView}; use wit::Extension; -pub use wit::ExtensionProject; pub struct WasmHost { engine: Engine, @@ -238,6 +237,25 @@ impl extension::Extension for WasmExtension { .await } + async fn context_server_command( + &self, + context_server_id: Arc, + project: Arc, + ) -> Result { + self.call(|extension, store| { + async move { + let project_resource = store.data_mut().table().push(project)?; + let command = extension + .call_context_server_command(store, context_server_id.clone(), project_resource) + .await? + .map_err(|err| anyhow!("{err}"))?; + anyhow::Ok(command.into()) + } + .boxed() + }) + .await + } + async fn suggest_docs_packages(&self, provider: Arc) -> Result> { self.call(|extension, store| { async move { diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs index 02577abd0e..234eec26ec 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs @@ -8,7 +8,7 @@ use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; use context_servers::manager::ContextServerSettings; -use extension::{KeyValueStoreDelegate, WorktreeDelegate}; +use extension::{KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate}; use futures::{io::BufReader, FutureExt as _}; use futures::{lock::Mutex, AsyncReadExt}; use language::{language_settings::AllLanguageSettings, LanguageName, LanguageServerBinaryStatus}; @@ -44,13 +44,10 @@ mod settings { } pub type ExtensionWorktree = Arc; +pub type ExtensionProject = Arc; pub type ExtensionKeyValueStore = Arc; pub type ExtensionHttpResponseStream = Arc>>; -pub struct ExtensionProject { - pub worktree_ids: Vec, -} - pub fn linker() -> &'static Linker { static LINKER: OnceLock> = OnceLock::new(); LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker)) @@ -273,7 +270,7 @@ impl HostProject for WasmState { project: Resource, ) -> wasmtime::Result> { let project = self.table.get(&project)?; - Ok(project.worktree_ids.clone()) + Ok(project.worktree_ids()) } fn drop(&mut self, _project: Resource) -> Result<()> { diff --git a/crates/extensions_ui/Cargo.toml b/crates/extensions_ui/Cargo.toml index e8de7c3f12..a219fe4bd4 100644 --- a/crates/extensions_ui/Cargo.toml +++ b/crates/extensions_ui/Cargo.toml @@ -41,7 +41,6 @@ theme.workspace = true ui.workspace = true util.workspace = true vim_mode_setting.workspace = true -wasmtime-wasi.workspace = true workspace.workspace = true zed_actions.workspace = true diff --git a/crates/extensions_ui/src/extension_registration_hooks.rs b/crates/extensions_ui/src/extension_registration_hooks.rs index 07a4c1455c..1b427cd187 100644 --- a/crates/extensions_ui/src/extension_registration_hooks.rs +++ b/crates/extensions_ui/src/extension_registration_hooks.rs @@ -1,13 +1,11 @@ use std::{path::PathBuf, sync::Arc}; -use anyhow::{anyhow, Result}; +use anyhow::Result; use assistant_slash_command::{ExtensionSlashCommand, SlashCommandRegistry}; use context_servers::manager::ServerCommand; use context_servers::ContextServerFactoryRegistry; -use db::smol::future::FutureExt as _; -use extension::Extension; -use extension_host::wasm_host::ExtensionProject; -use extension_host::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host}; +use extension::{Extension, ProjectDelegate}; +use extension_host::extension_lsp_adapter::ExtensionLspAdapter; use fs::Fs; use gpui::{AppContext, BackgroundExecutor, Model, Task}; use indexed_docs::{ExtensionIndexedDocsProvider, IndexedDocsRegistry, ProviderId}; @@ -16,7 +14,16 @@ use lsp::LanguageServerName; use snippet_provider::SnippetRegistry; use theme::{ThemeRegistry, ThemeSettings}; use ui::SharedString; -use wasmtime_wasi::WasiView as _; + +struct ExtensionProject { + worktree_ids: Vec, +} + +impl ProjectDelegate for ExtensionProject { + fn worktree_ids(&self) -> Vec { + self.worktree_ids.clone() + } +} pub struct ConcreteExtensionRegistrationHooks { slash_command_registry: Arc, @@ -72,8 +79,8 @@ impl extension_host::ExtensionRegistrationHooks for ConcreteExtensionRegistratio fn register_context_server( &self, + extension: Arc, id: Arc, - extension: wasm_host::WasmExtension, cx: &mut AppContext, ) { self.context_server_factory_registry @@ -84,42 +91,24 @@ impl extension_host::ExtensionRegistrationHooks for ConcreteExtensionRegistratio move |project, cx| { log::info!( "loading command for context server {id} from extension {}", - extension.manifest.id + extension.manifest().id ); let id = id.clone(); let extension = extension.clone(); cx.spawn(|mut cx| async move { let extension_project = - project.update(&mut cx, |project, cx| ExtensionProject { - worktree_ids: project - .visible_worktrees(cx) - .map(|worktree| worktree.read(cx).id().to_proto()) - .collect(), + project.update(&mut cx, |project, cx| { + Arc::new(ExtensionProject { + worktree_ids: project + .visible_worktrees(cx) + .map(|worktree| worktree.read(cx).id().to_proto()) + .collect(), + }) })?; let command = extension - .call({ - let id = id.clone(); - |extension, store| { - async move { - let project = store - .data_mut() - .table() - .push(extension_project)?; - let command = extension - .call_context_server_command( - store, - id.clone(), - project, - ) - .await? - .map_err(|e| anyhow!("{}", e))?; - anyhow::Ok(command) - } - .boxed() - } - }) + .context_server_command(id.clone(), extension_project) .await?; log::info!("loaded command for context server {id}: {command:?}"); From 659b1c9dcf42c1a1074b090ae9eabb757a6aab94 Mon Sep 17 00:00:00 2001 From: Hugo Cardante <65104945+omennia@users.noreply.github.com> Date: Fri, 22 Nov 2024 18:45:42 +0000 Subject: [PATCH 129/157] Add the option to hide both the task and command lines in the task output (#20920) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The goal is to be able to hide these lines from a task output: ```sh ⏵ Task `...` finished successfully ⏵ Command: ... ``` --------- Co-authored-by: Peter Tripp --- crates/project/src/terminals.rs | 2 ++ crates/task/src/lib.rs | 4 ++++ crates/task/src/task_template.rs | 9 +++++++++ crates/terminal/src/terminal.rs | 23 ++++++++++++++++++----- docs/src/tasks.md | 6 +++++- 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 1320a883f3..111516c82d 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -174,6 +174,8 @@ impl Project { command_label: spawn_task.command_label, hide: spawn_task.hide, status: TaskStatus::Running, + show_summary: spawn_task.show_summary, + show_command: spawn_task.show_command, completion_rx, }); diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index 534b77b743..cf3a414b11 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -51,6 +51,10 @@ pub struct SpawnInTerminal { pub hide: HideStrategy, /// Which shell to use when spawning the task. pub shell: Shell, + /// Whether to show the task summary line in the task output (sucess/failure). + pub show_summary: bool, + /// Whether to show the command line in the task output. + pub show_command: bool, } /// A final form of the [`TaskTemplate`], that got resolved with a particualar [`TaskContext`] and now is ready to spawn the actual task. diff --git a/crates/task/src/task_template.rs b/crates/task/src/task_template.rs index b72a0d25f8..23a2bc8ca7 100644 --- a/crates/task/src/task_template.rs +++ b/crates/task/src/task_template.rs @@ -1,4 +1,5 @@ use std::path::PathBuf; +use util::serde::default_true; use anyhow::{bail, Context}; use collections::{HashMap, HashSet}; @@ -57,6 +58,12 @@ pub struct TaskTemplate { /// Which shell to use when spawning the task. #[serde(default)] pub shell: Shell, + /// Whether to show the task line in the task output. + #[serde(default = "default_true")] + pub show_summary: bool, + /// Whether to show the command line in the task output. + #[serde(default = "default_true")] + pub show_command: bool, } /// What to do with the terminal pane and tab, after the command was started. @@ -230,6 +237,8 @@ impl TaskTemplate { reveal: self.reveal, hide: self.hide, shell: self.shell.clone(), + show_summary: self.show_summary, + show_command: self.show_command, }), }) } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 5a15723cee..6610ac567d 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -639,6 +639,8 @@ pub struct TaskState { pub status: TaskStatus, pub completion_rx: Receiver<()>, pub hide: HideStrategy, + pub show_summary: bool, + pub show_command: bool, } /// A status of the current terminal tab's task. @@ -1760,11 +1762,22 @@ impl Terminal { }; let (finished_successfully, task_line, command_line) = task_summary(task, error_code); - // SAFETY: the invocation happens on non `TaskStatus::Running` tasks, once, - // after either `AlacTermEvent::Exit` or `AlacTermEvent::ChildExit` events that are spawned - // when Zed task finishes and no more output is made. - // After the task summary is output once, no more text is appended to the terminal. - unsafe { append_text_to_term(&mut self.term.lock(), &[&task_line, &command_line]) }; + let mut lines_to_show = Vec::new(); + if task.show_summary { + lines_to_show.push(task_line.as_str()); + } + if task.show_command { + lines_to_show.push(command_line.as_str()); + } + + if !lines_to_show.is_empty() { + // SAFETY: the invocation happens on non `TaskStatus::Running` tasks, once, + // after either `AlacTermEvent::Exit` or `AlacTermEvent::ChildExit` events that are spawned + // when Zed task finishes and no more output is made. + // After the task summary is output once, no more text is appended to the terminal. + unsafe { append_text_to_term(&mut self.term.lock(), &lines_to_show) }; + } + match task.hide { HideStrategy::Never => {} HideStrategy::Always => { diff --git a/docs/src/tasks.md b/docs/src/tasks.md index 98cbd6dfc1..f32e5778fc 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -41,7 +41,11 @@ Zed supports ways to spawn (and rerun) commands using its integrated terminal to // "args": ["--login"] // } // } - "shell": "system" + "shell": "system", + // Whether to show the task line in the output of the spawned task, defaults to `true`. + "show_summary": true, + // Whether to show the command line in the output of the spawned task, defaults to `true`. + "show_output": true } ] ``` From 23321be2ceda03548368ff262f81f905b86314df Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Fri, 22 Nov 2024 18:58:24 +0000 Subject: [PATCH 130/157] docs: Improve Dart language docs (#21071) --- docs/src/languages/dart.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/src/languages/dart.md b/docs/src/languages/dart.md index 32f312e5dd..6b7d01c39e 100644 --- a/docs/src/languages/dart.md +++ b/docs/src/languages/dart.md @@ -5,9 +5,22 @@ Dart support is available through the [Dart extension](https://github.com/zed-ex - Tree Sitter: [UserNobody14/tree-sitter-dart](https://github.com/UserNobody14/tree-sitter-dart) - Language Server: [dart language-server](https://github.com/dart-lang/sdk) +## Pre-requisites + +You will need to install the Dart SDK. + +You can install dart from [dart.dev/get-dart](https://dart.dev/get-dart) or via the [Flutter Version Management CLI (fvm)](https://fvm.app/documentation/getting-started/installation) + ## Configuration -The `dart` binary can be configured in a Zed settings file with: +The dart extension requires no configuration if you have `dart` in your path: + +```sh +which dart +dart --version +``` + +If you would like to use a specific dart binary or use dart via FVM you can specify the `dart` binary in your Zed settings.jsons file: ```json { @@ -22,7 +35,4 @@ The `dart` binary can be configured in a Zed settings file with: } ``` - +Please see the Dart documentation for more information on [dart language-server capabilities](https://github.com/dart-lang/sdk/blob/main/pkg/analysis_server/tool/lsp_spec/README.md). From 2fd210bc9aece3c33f958317e3f141ead660569c Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Fri, 22 Nov 2024 21:10:51 +0000 Subject: [PATCH 131/157] Fix stale Discord invite links (#21074) --- crates/gpui/README.md | 2 +- crates/gpui/src/gpui.rs | 2 +- docs/src/remote-development.md | 4 ++-- docs/src/windows.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/gpui/README.md b/crates/gpui/README.md index 3ca0dcf7ca..6c0a5b607c 100644 --- a/crates/gpui/README.md +++ b/crates/gpui/README.md @@ -61,4 +61,4 @@ In addition to the systems above, GPUI provides a range of smaller services that - The `[gpui::test]` macro provides a convenient way to write tests for your GPUI applications. Tests also have their own kind of context, a `TestAppContext` which provides ways of simulating common platform input. See `app::test_context` and `test` modules for more details. -Currently, the best way to learn about these APIs is to read the Zed source code, ask us about it at a fireside hack, or drop a question in the [Zed Discord](https://discord.gg/zed-community). We're working on improving the documentation, creating more examples, and will be publishing more guides to GPUI on our [blog](https://zed.dev/blog). +Currently, the best way to learn about these APIs is to read the Zed source code, ask us about it at a fireside hack, or drop a question in the [Zed Discord](https://zed.dev/community-links). We're working on improving the documentation, creating more examples, and will be publishing more guides to GPUI on our [blog](https://zed.dev/blog). diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 2952f4af8a..51e2c3f173 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -56,7 +56,7 @@ //! and [`test`] modules for more details. //! //! Currently, the best way to learn about these APIs is to read the Zed source code, ask us about it at a fireside hack, or drop -//! a question in the [Zed Discord](https://discord.gg/zed-community). We're working on improving the documentation, creating more examples, +//! a question in the [Zed Discord](https://zed.dev/community-links). We're working on improving the documentation, creating more examples, //! and will be publishing more guides to GPUI on our [blog](https://zed.dev/blog). #![deny(missing_docs)] diff --git a/docs/src/remote-development.md b/docs/src/remote-development.md index 17ae23bb63..7ab0cb6b76 100644 --- a/docs/src/remote-development.md +++ b/docs/src/remote-development.md @@ -125,7 +125,7 @@ Each connection tries to run the development server in proxy mode. This mode wil In the case that reconnecting fails, the daemon will not be re-used. That said, unsaved changes are by default persisted locally, so that you do not lose work. You can always reconnect to the project at a later date and Zed will restore unsaved changes. -If you are struggling with connection issues, you should be able to see more information in the Zed log `cmd-shift-p Open Log`. If you are seeing things that are unexpected, please file a [GitHub issue](https://github.com/zed-industries/zed/issues/new) or reach out in the #remoting-feedback channel in the [Zed Discord](https://discord.gg/zed-community). +If you are struggling with connection issues, you should be able to see more information in the Zed log `cmd-shift-p Open Log`. If you are seeing things that are unexpected, please file a [GitHub issue](https://github.com/zed-industries/zed/issues/new) or reach out in the #remoting-feedback channel in the [Zed Discord](https://zed.dev/community-links). ## Supported SSH Options @@ -152,4 +152,4 @@ Note that we deliberately disallow some options (for example `-t` or `-T`) that ## Feedback -Please join the #remoting-feedback channel in the [Zed Discord](https://discord.gg/zed-community). +Please join the #remoting-feedback channel in the [Zed Discord](https://zed.dev/community-links). diff --git a/docs/src/windows.md b/docs/src/windows.md index 47fae7cb9f..f8949f22f3 100644 --- a/docs/src/windows.md +++ b/docs/src/windows.md @@ -10,4 +10,4 @@ Zed Employees are not currently working on the Windows build. However, we welcome contributions from the community to improve Windows support. - [GitHub Issues with 'Windows' label](https://github.com/zed-industries/zed/issues?q=is%3Aissue+is%3Aopen+label%3Awindows) -- [Zed Community Discord](https://discord.gg/zed-community) -> `#windows-port` +- [Zed Community Discord](https://zed.dev/community-links) -> `#windows-port` From 1a0a8a9559cf0a63f4be7c564603b7da78101b57 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Fri, 22 Nov 2024 22:45:03 +0000 Subject: [PATCH 132/157] Fix picker new_path_prompt throwing "file exists" when saving (#21080) Fix for getting File exists "os error 17" with `"use_system_path_prompts": false,` This was reproducible when the first worktree is a non-folder worktree (e.g. setting.json) so we were trying to create the new file with a path under ~/.config/zed/settings.json/newfile.ext Co-authored-by: Conrad Irwin --- crates/file_finder/src/new_path_prompt.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/file_finder/src/new_path_prompt.rs b/crates/file_finder/src/new_path_prompt.rs index d4492857b4..6a1b08e205 100644 --- a/crates/file_finder/src/new_path_prompt.rs +++ b/crates/file_finder/src/new_path_prompt.rs @@ -71,8 +71,16 @@ impl Match { fn project_path(&self, project: &Project, cx: &WindowContext) -> Option { let worktree_id = if let Some(path_match) = &self.path_match { WorktreeId::from_usize(path_match.worktree_id) + } else if let Some(worktree) = project.visible_worktrees(cx).find(|worktree| { + worktree + .read(cx) + .root_entry() + .is_some_and(|entry| entry.is_dir()) + }) { + worktree.read(cx).id() } else { - project.worktrees(cx).next()?.read(cx).id() + // todo(): we should find_or_create a workspace. + return None; }; let path = PathBuf::from(self.relative_path()); From becc36380f19de4148980419a9fdc4ba1b36e5f8 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Fri, 22 Nov 2024 22:46:14 +0000 Subject: [PATCH 133/157] Cleanup file_scan_inclusions in default.json (#21073) --- assets/settings/default.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 819cdcfff6..02527e8e67 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -683,10 +683,7 @@ // ignored by git. This is useful for files that are not tracked by git, // but are still important to your project. Note that globs that are // overly broad can slow down Zed's file scanning. Overridden by `file_scan_exclusions`. - "file_scan_inclusions": [ - ".env*", - "docker-compose.*.yml" - ], + "file_scan_inclusions": [".env*"], // Git gutter behavior configuration. "git": { // Control whether the git gutter is shown. May take 2 values: From 96854c68eadd1dd72aa379368fde3cea791498a4 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 22 Nov 2024 14:49:26 -0800 Subject: [PATCH 134/157] Markdown preview image rendering (#21082) Closes https://github.com/zed-industries/zed/issues/13246 Supersedes: https://github.com/zed-industries/zed/pull/16192 I couldn't push to the git fork this user was using, so here's the exact same PR but with some style nits implemented. Release Notes: - Added image rendering to the Markdown preview --------- Co-authored-by: dovakin0007 Co-authored-by: dovakin0007 <73059450+dovakin0007@users.noreply.github.com> --- crates/language/src/markdown.rs | 14 +- .../markdown_preview/src/markdown_elements.rs | 137 +++++++- .../markdown_preview/src/markdown_parser.rs | 215 +++++++----- .../markdown_preview/src/markdown_renderer.rs | 330 ++++++++++++++---- .../notifications/src/notification_store.rs | 7 +- crates/repl/src/notebook/cell.rs | 2 +- crates/rich_text/src/rich_text.rs | 14 +- 7 files changed, 538 insertions(+), 181 deletions(-) diff --git a/crates/language/src/markdown.rs b/crates/language/src/markdown.rs index b9393a16ab..0221f0f431 100644 --- a/crates/language/src/markdown.rs +++ b/crates/language/src/markdown.rs @@ -239,12 +239,7 @@ pub async fn parse_markdown_block( Event::Start(tag) => match tag { Tag::Paragraph => new_paragraph(text, &mut list_stack), - Tag::Heading { - level: _, - id: _, - classes: _, - attrs: _, - } => { + Tag::Heading { .. } => { new_paragraph(text, &mut list_stack); bold_depth += 1; } @@ -267,12 +262,7 @@ pub async fn parse_markdown_block( Tag::Strikethrough => strikethrough_depth += 1, - Tag::Link { - link_type: _, - dest_url, - title: _, - id: _, - } => link_url = Some(dest_url.to_string()), + Tag::Link { dest_url, .. } => link_url = Some(dest_url.to_string()), Tag::List(number) => { list_stack.push((number, false)); diff --git a/crates/markdown_preview/src/markdown_elements.rs b/crates/markdown_preview/src/markdown_elements.rs index 8423e4ec82..ff43fab08a 100644 --- a/crates/markdown_preview/src/markdown_elements.rs +++ b/crates/markdown_preview/src/markdown_elements.rs @@ -13,7 +13,7 @@ pub enum ParsedMarkdownElement { BlockQuote(ParsedMarkdownBlockQuote), CodeBlock(ParsedMarkdownCodeBlock), /// A paragraph of text and other inline elements. - Paragraph(ParsedMarkdownText), + Paragraph(MarkdownParagraph), HorizontalRule(Range), } @@ -25,7 +25,13 @@ impl ParsedMarkdownElement { Self::Table(table) => table.source_range.clone(), Self::BlockQuote(block_quote) => block_quote.source_range.clone(), Self::CodeBlock(code_block) => code_block.source_range.clone(), - Self::Paragraph(text) => text.source_range.clone(), + Self::Paragraph(text) => match &text[0] { + MarkdownParagraphChunk::Text(t) => t.source_range.clone(), + MarkdownParagraphChunk::Image(image) => match image { + Image::Web { source_range, .. } => source_range.clone(), + Image::Path { source_range, .. } => source_range.clone(), + }, + }, Self::HorizontalRule(range) => range.clone(), } } @@ -35,6 +41,15 @@ impl ParsedMarkdownElement { } } +pub type MarkdownParagraph = Vec; + +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq))] +pub enum MarkdownParagraphChunk { + Text(ParsedMarkdownText), + Image(Image), +} + #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] pub struct ParsedMarkdown { @@ -73,7 +88,7 @@ pub struct ParsedMarkdownCodeBlock { pub struct ParsedMarkdownHeading { pub source_range: Range, pub level: HeadingLevel, - pub contents: ParsedMarkdownText, + pub contents: MarkdownParagraph, } #[derive(Debug, PartialEq)] @@ -107,7 +122,7 @@ pub enum ParsedMarkdownTableAlignment { #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] pub struct ParsedMarkdownTableRow { - pub children: Vec, + pub children: Vec, } impl Default for ParsedMarkdownTableRow { @@ -123,7 +138,7 @@ impl ParsedMarkdownTableRow { } } - pub fn with_children(children: Vec) -> Self { + pub fn with_children(children: Vec) -> Self { Self { children } } } @@ -135,7 +150,7 @@ pub struct ParsedMarkdownBlockQuote { pub children: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ParsedMarkdownText { /// Where the text is located in the source Markdown document. pub source_range: Range, @@ -266,10 +281,112 @@ impl Display for Link { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Link::Web { url } => write!(f, "{}", url), - Link::Path { - display_path, - path: _, - } => write!(f, "{}", display_path.display()), + Link::Path { display_path, .. } => write!(f, "{}", display_path.display()), + } + } +} + +/// A Markdown Image +#[derive(Debug, Clone)] +#[cfg_attr(test, derive(PartialEq))] +pub enum Image { + Web { + source_range: Range, + /// The URL of the Image. + url: String, + /// Link URL if exists. + link: Option, + /// alt text if it exists + alt_text: Option, + }, + /// Image path on the filesystem. + Path { + source_range: Range, + /// The path as provided in the Markdown document. + display_path: PathBuf, + /// The absolute path to the item. + path: PathBuf, + /// Link URL if exists. + link: Option, + /// alt text if it exists + alt_text: Option, + }, +} + +impl Image { + pub fn identify( + source_range: Range, + file_location_directory: Option, + text: String, + link: Option, + ) -> Option { + if text.starts_with("http") { + return Some(Image::Web { + source_range, + url: text, + link, + alt_text: None, + }); + } + let path = PathBuf::from(&text); + if path.is_absolute() { + return Some(Image::Path { + source_range, + display_path: path.clone(), + path, + link, + alt_text: None, + }); + } + if let Some(file_location_directory) = file_location_directory { + let display_path = path; + let path = file_location_directory.join(text); + return Some(Image::Path { + source_range, + display_path, + path, + link, + alt_text: None, + }); + } + None + } + + pub fn with_alt_text(&self, alt_text: ParsedMarkdownText) -> Self { + match self { + Image::Web { + ref source_range, + ref url, + ref link, + .. + } => Image::Web { + source_range: source_range.clone(), + url: url.clone(), + link: link.clone(), + alt_text: Some(alt_text), + }, + Image::Path { + ref source_range, + ref display_path, + ref path, + ref link, + .. + } => Image::Path { + source_range: source_range.clone(), + display_path: display_path.clone(), + path: path.clone(), + link: link.clone(), + alt_text: Some(alt_text), + }, + } + } +} + +impl Display for Image { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Image::Web { url, .. } => write!(f, "{}", url), + Image::Path { display_path, .. } => write!(f, "{}", display_path.display()), } } } diff --git a/crates/markdown_preview/src/markdown_parser.rs b/crates/markdown_preview/src/markdown_parser.rs index d514b89e52..211cca2494 100644 --- a/crates/markdown_preview/src/markdown_parser.rs +++ b/crates/markdown_preview/src/markdown_parser.rs @@ -4,7 +4,7 @@ use collections::FxHashMap; use gpui::FontWeight; use language::LanguageRegistry; use pulldown_cmark::{Alignment, Event, Options, Parser, Tag, TagEnd}; -use std::{ops::Range, path::PathBuf, sync::Arc}; +use std::{ops::Range, path::PathBuf, sync::Arc, vec}; pub async fn parse_markdown( markdown_input: &str, @@ -101,11 +101,11 @@ impl<'a> MarkdownParser<'a> { | Event::Code(_) | Event::Html(_) | Event::FootnoteReference(_) - | Event::Start(Tag::Link { link_type: _, dest_url: _, title: _, id: _ }) + | Event::Start(Tag::Link { .. }) | Event::Start(Tag::Emphasis) | Event::Start(Tag::Strong) | Event::Start(Tag::Strikethrough) - | Event::Start(Tag::Image { link_type: _, dest_url: _, title: _, id: _ }) => { + | Event::Start(Tag::Image { .. }) => { true } _ => false, @@ -134,12 +134,7 @@ impl<'a> MarkdownParser<'a> { let text = self.parse_text(false, Some(source_range)); Some(vec![ParsedMarkdownElement::Paragraph(text)]) } - Tag::Heading { - level, - id: _, - classes: _, - attrs: _, - } => { + Tag::Heading { level, .. } => { let level = *level; self.cursor += 1; let heading = self.parse_heading(level); @@ -194,22 +189,23 @@ impl<'a> MarkdownParser<'a> { &mut self, should_complete_on_soft_break: bool, source_range: Option>, - ) -> ParsedMarkdownText { + ) -> MarkdownParagraph { let source_range = source_range.unwrap_or_else(|| { self.current() .map(|(_, range)| range.clone()) .unwrap_or_default() }); + let mut markdown_text_like = Vec::new(); let mut text = String::new(); let mut bold_depth = 0; let mut italic_depth = 0; let mut strikethrough_depth = 0; let mut link: Option = None; + let mut image: Option = None; let mut region_ranges: Vec> = vec![]; let mut regions: Vec = vec![]; let mut highlights: Vec<(Range, MarkdownHighlight)> = vec![]; - let mut link_urls: Vec = vec![]; let mut link_ranges: Vec> = vec![]; @@ -225,8 +221,6 @@ impl<'a> MarkdownParser<'a> { if should_complete_on_soft_break { break; } - - // `Some text\nSome more text` should be treated as a single line. text.push(' '); } @@ -240,7 +234,6 @@ impl<'a> MarkdownParser<'a> { Event::Text(t) => { text.push_str(t.as_ref()); - let mut style = MarkdownHighlightStyle::default(); if bold_depth > 0 { @@ -299,7 +292,6 @@ impl<'a> MarkdownParser<'a> { url: link.as_str().to_string(), }), }); - last_link_len = end; } last_link_len @@ -316,13 +308,63 @@ impl<'a> MarkdownParser<'a> { } } if new_highlight { - highlights - .push((last_run_len..text.len(), MarkdownHighlight::Style(style))); + highlights.push(( + last_run_len..text.len(), + MarkdownHighlight::Style(style.clone()), + )); } } - } + if let Some(mut image) = image.clone() { + let is_valid_image = match image.clone() { + Image::Path { display_path, .. } => { + gpui::ImageSource::try_from(display_path).is_ok() + } + Image::Web { url, .. } => gpui::ImageSource::try_from(url).is_ok(), + }; + if is_valid_image { + text.truncate(text.len() - t.len()); + if !t.is_empty() { + let alt_text = ParsedMarkdownText { + source_range: source_range.clone(), + contents: t.to_string(), + highlights: highlights.clone(), + region_ranges: region_ranges.clone(), + regions: regions.clone(), + }; + image = image.with_alt_text(alt_text); + } else { + let alt_text = ParsedMarkdownText { + source_range: source_range.clone(), + contents: "img".to_string(), + highlights: highlights.clone(), + region_ranges: region_ranges.clone(), + regions: regions.clone(), + }; + image = image.with_alt_text(alt_text); + } + if !text.is_empty() { + let parsed_regions = + MarkdownParagraphChunk::Text(ParsedMarkdownText { + source_range: source_range.clone(), + contents: text.clone(), + highlights: highlights.clone(), + region_ranges: region_ranges.clone(), + regions: regions.clone(), + }); + text = String::new(); + highlights = vec![]; + region_ranges = vec![]; + regions = vec![]; + markdown_text_like.push(parsed_regions); + } - // Note: This event means "inline code" and not "code block" + let parsed_image = MarkdownParagraphChunk::Image(image.clone()); + markdown_text_like.push(parsed_image); + style = MarkdownHighlightStyle::default(); + } + style.underline = true; + }; + } Event::Code(t) => { text.push_str(t.as_ref()); region_ranges.push(prev_len..text.len()); @@ -336,46 +378,44 @@ impl<'a> MarkdownParser<'a> { }), )); } - regions.push(ParsedRegion { code: true, link: link.clone(), }); } - Event::Start(tag) => match tag { Tag::Emphasis => italic_depth += 1, Tag::Strong => bold_depth += 1, Tag::Strikethrough => strikethrough_depth += 1, - Tag::Link { - link_type: _, - dest_url, - title: _, - id: _, - } => { + Tag::Link { dest_url, .. } => { link = Link::identify( self.file_location_directory.clone(), dest_url.to_string(), ); } + Tag::Image { dest_url, .. } => { + image = Image::identify( + source_range.clone(), + self.file_location_directory.clone(), + dest_url.to_string(), + link.clone(), + ); + } _ => { break; } }, Event::End(tag) => match tag { - TagEnd::Emphasis => { - italic_depth -= 1; - } - TagEnd::Strong => { - bold_depth -= 1; - } - TagEnd::Strikethrough => { - strikethrough_depth -= 1; - } + TagEnd::Emphasis => italic_depth -= 1, + TagEnd::Strong => bold_depth -= 1, + TagEnd::Strikethrough => strikethrough_depth -= 1, TagEnd::Link => { link = None; } + TagEnd::Image => { + image = None; + } TagEnd::Paragraph => { self.cursor += 1; break; @@ -384,7 +424,6 @@ impl<'a> MarkdownParser<'a> { break; } }, - _ => { break; } @@ -392,14 +431,16 @@ impl<'a> MarkdownParser<'a> { self.cursor += 1; } - - ParsedMarkdownText { - source_range, - contents: text, - highlights, - regions, - region_ranges, + if !text.is_empty() { + markdown_text_like.push(MarkdownParagraphChunk::Text(ParsedMarkdownText { + source_range: source_range.clone(), + contents: text, + highlights, + regions, + region_ranges, + })); } + markdown_text_like } fn parse_heading(&mut self, level: pulldown_cmark::HeadingLevel) -> ParsedMarkdownHeading { @@ -708,7 +749,6 @@ impl<'a> MarkdownParser<'a> { } } } - let highlights = if let Some(language) = &language { if let Some(registry) = &self.language_registry { let rope: language::Rope = code.as_str().into(); @@ -735,10 +775,14 @@ impl<'a> MarkdownParser<'a> { #[cfg(test)] mod tests { + use core::panic; + use super::*; use gpui::BackgroundExecutor; - use language::{tree_sitter_rust, HighlightId, Language, LanguageConfig, LanguageMatcher}; + use language::{ + tree_sitter_rust, HighlightId, Language, LanguageConfig, LanguageMatcher, LanguageRegistry, + }; use pretty_assertions::assert_eq; use ParsedMarkdownListItemType::*; @@ -810,20 +854,29 @@ mod tests { assert_eq!(parsed.children.len(), 1); assert_eq!( parsed.children[0], - ParsedMarkdownElement::Paragraph(ParsedMarkdownText { - source_range: 0..35, - contents: "Some bostrikethroughld text".to_string(), - highlights: Vec::new(), - region_ranges: Vec::new(), - regions: Vec::new(), - }) + ParsedMarkdownElement::Paragraph(vec![MarkdownParagraphChunk::Text( + ParsedMarkdownText { + source_range: 0..35, + contents: "Some bostrikethroughld text".to_string(), + highlights: Vec::new(), + region_ranges: Vec::new(), + regions: Vec::new(), + } + )]) ); - let paragraph = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] { + let new_text = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] { text } else { panic!("Expected a paragraph"); }; + + let paragraph = if let MarkdownParagraphChunk::Text(text) = &new_text[0] { + text + } else { + panic!("Expected a text"); + }; + assert_eq!( paragraph.highlights, vec![ @@ -871,6 +924,11 @@ mod tests { parsed.children, vec![p("Checkout this https://zed.dev link", 0..34)] ); + } + + #[gpui::test] + async fn test_image_links_detection() { + let parsed = parse("![test](https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png)").await; let paragraph = if let ParsedMarkdownElement::Paragraph(text) = &parsed.children[0] { text @@ -878,25 +936,22 @@ mod tests { panic!("Expected a paragraph"); }; assert_eq!( - paragraph.highlights, - vec![( - 14..29, - MarkdownHighlight::Style(MarkdownHighlightStyle { - underline: true, - ..Default::default() - }), - )] + paragraph[0], + MarkdownParagraphChunk::Image(Image::Web { + source_range: 0..111, + url: "https://blog.logrocket.com/wp-content/uploads/2024/04/exploring-zed-open-source-code-editor-rust-2.png".to_string(), + link: None, + alt_text: Some( + ParsedMarkdownText { + source_range: 0..111, + contents: "test".to_string(), + highlights: vec![], + region_ranges: vec![], + regions: vec![], + }, + ), + },) ); - assert_eq!( - paragraph.regions, - vec![ParsedRegion { - code: false, - link: Some(Link::Web { - url: "https://zed.dev".to_string() - }), - }] - ); - assert_eq!(paragraph.region_ranges, vec![14..29]); } #[gpui::test] @@ -1169,7 +1224,7 @@ Some other content vec![ list_item(0..8, 1, Unordered, vec![p("code", 2..8)]), list_item(9..19, 1, Unordered, vec![p("bold", 11..19)]), - list_item(20..49, 1, Unordered, vec![p("link", 22..49)],) + list_item(20..49, 1, Unordered, vec![p("link", 22..49)],), ], ); } @@ -1312,7 +1367,7 @@ fn main() { )) } - fn h1(contents: ParsedMarkdownText, source_range: Range) -> ParsedMarkdownElement { + fn h1(contents: MarkdownParagraph, source_range: Range) -> ParsedMarkdownElement { ParsedMarkdownElement::Heading(ParsedMarkdownHeading { source_range, level: HeadingLevel::H1, @@ -1320,7 +1375,7 @@ fn main() { }) } - fn h2(contents: ParsedMarkdownText, source_range: Range) -> ParsedMarkdownElement { + fn h2(contents: MarkdownParagraph, source_range: Range) -> ParsedMarkdownElement { ParsedMarkdownElement::Heading(ParsedMarkdownHeading { source_range, level: HeadingLevel::H2, @@ -1328,7 +1383,7 @@ fn main() { }) } - fn h3(contents: ParsedMarkdownText, source_range: Range) -> ParsedMarkdownElement { + fn h3(contents: MarkdownParagraph, source_range: Range) -> ParsedMarkdownElement { ParsedMarkdownElement::Heading(ParsedMarkdownHeading { source_range, level: HeadingLevel::H3, @@ -1340,14 +1395,14 @@ fn main() { ParsedMarkdownElement::Paragraph(text(contents, source_range)) } - fn text(contents: &str, source_range: Range) -> ParsedMarkdownText { - ParsedMarkdownText { + fn text(contents: &str, source_range: Range) -> MarkdownParagraph { + vec![MarkdownParagraphChunk::Text(ParsedMarkdownText { highlights: Vec::new(), region_ranges: Vec::new(), regions: Vec::new(), source_range, contents: contents.to_string(), - } + })] } fn block_quote( @@ -1401,7 +1456,7 @@ fn main() { } } - fn row(children: Vec) -> ParsedMarkdownTableRow { + fn row(children: Vec) -> ParsedMarkdownTableRow { ParsedMarkdownTableRow { children } } diff --git a/crates/markdown_preview/src/markdown_renderer.rs b/crates/markdown_preview/src/markdown_renderer.rs index 37ca5636a6..6140372e0b 100644 --- a/crates/markdown_preview/src/markdown_renderer.rs +++ b/crates/markdown_preview/src/markdown_renderer.rs @@ -1,29 +1,33 @@ use crate::markdown_elements::{ - HeadingLevel, Link, ParsedMarkdown, ParsedMarkdownBlockQuote, ParsedMarkdownCodeBlock, - ParsedMarkdownElement, ParsedMarkdownHeading, ParsedMarkdownListItem, - ParsedMarkdownListItemType, ParsedMarkdownTable, ParsedMarkdownTableAlignment, - ParsedMarkdownTableRow, ParsedMarkdownText, + HeadingLevel, Image, Link, MarkdownParagraph, MarkdownParagraphChunk, ParsedMarkdown, + ParsedMarkdownBlockQuote, ParsedMarkdownCodeBlock, ParsedMarkdownElement, + ParsedMarkdownHeading, ParsedMarkdownListItem, ParsedMarkdownListItemType, ParsedMarkdownTable, + ParsedMarkdownTableAlignment, ParsedMarkdownTableRow, ParsedMarkdownText, }; use gpui::{ - div, px, rems, AbsoluteLength, AnyElement, ClipboardItem, DefiniteLength, Div, Element, - ElementId, HighlightStyle, Hsla, InteractiveText, IntoElement, Keystroke, Length, Modifiers, - ParentElement, SharedString, Styled, StyledText, TextStyle, WeakView, WindowContext, + div, img, px, rems, AbsoluteLength, AnyElement, ClipboardItem, DefiniteLength, Div, Element, + ElementId, HighlightStyle, Hsla, ImageSource, InteractiveText, IntoElement, Keystroke, Length, + Modifiers, ParentElement, Resource, SharedString, Styled, StyledText, TextStyle, WeakView, + WindowContext, }; use settings::Settings; use std::{ ops::{Mul, Range}, + path::Path, sync::Arc, + vec, }; use theme::{ActiveTheme, SyntaxTheme, ThemeSettings}; use ui::{ h_flex, relative, v_flex, Checkbox, Clickable, FluentBuilder, IconButton, IconName, IconSize, - InteractiveElement, LinkPreview, Selection, StatefulInteractiveElement, StyledExt, Tooltip, - VisibleOnHover, + InteractiveElement, LinkPreview, Selection, StatefulInteractiveElement, StyledExt, StyledImage, + Tooltip, VisibleOnHover, }; use workspace::Workspace; type CheckboxClickedCallback = Arc, &mut WindowContext)>>; +#[derive(Clone)] pub struct RenderContext { workspace: Option>, next_id: usize, @@ -153,7 +157,7 @@ fn render_markdown_heading(parsed: &ParsedMarkdownHeading, cx: &mut RenderContex .text_color(color) .pt(rems(0.15)) .pb_1() - .child(render_markdown_text(&parsed.contents, cx)) + .children(render_markdown_text(&parsed.contents, cx)) .whitespace_normal() .into_any() } @@ -231,17 +235,29 @@ fn render_markdown_list_item( cx.with_common_p(item).into_any() } +fn paragraph_len(paragraphs: &MarkdownParagraph) -> usize { + paragraphs + .iter() + .map(|paragraph| match paragraph { + MarkdownParagraphChunk::Text(text) => text.contents.len(), + // TODO: Scale column width based on image size + MarkdownParagraphChunk::Image(_) => 1, + }) + .sum() +} + fn render_markdown_table(parsed: &ParsedMarkdownTable, cx: &mut RenderContext) -> AnyElement { let mut max_lengths: Vec = vec![0; parsed.header.children.len()]; for (index, cell) in parsed.header.children.iter().enumerate() { - let length = cell.contents.len(); + let length = paragraph_len(&cell); max_lengths[index] = length; } for row in &parsed.body { for (index, cell) in row.children.iter().enumerate() { - let length = cell.contents.len(); + let length = paragraph_len(&cell); + if length > max_lengths[index] { max_lengths[index] = length; } @@ -307,11 +323,10 @@ fn render_markdown_table_row( }; let max_width = max_column_widths.get(index).unwrap_or(&0.0); - let mut cell = container .w(Length::Definite(relative(*max_width))) .h_full() - .child(contents) + .children(contents) .px_2() .py_1() .border_color(cx.border_color); @@ -398,18 +413,219 @@ fn render_markdown_code_block( .into_any() } -fn render_markdown_paragraph(parsed: &ParsedMarkdownText, cx: &mut RenderContext) -> AnyElement { +fn render_markdown_paragraph(parsed: &MarkdownParagraph, cx: &mut RenderContext) -> AnyElement { cx.with_common_p(div()) - .child(render_markdown_text(parsed, cx)) + .children(render_markdown_text(parsed, cx)) + .flex() .into_any_element() } -fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) -> AnyElement { - let element_id = cx.next_id(&parsed.source_range); +fn render_markdown_text(parsed_new: &MarkdownParagraph, cx: &mut RenderContext) -> Vec { + let mut any_element = vec![]; + // these values are cloned in-order satisfy borrow checker + let syntax_theme = cx.syntax_theme.clone(); + let workspace_clone = cx.workspace.clone(); + let code_span_bg_color = cx.code_span_background_color; + let text_style = cx.text_style.clone(); + + for parsed_region in parsed_new { + match parsed_region { + MarkdownParagraphChunk::Text(parsed) => { + let element_id = cx.next_id(&parsed.source_range); + + let highlights = gpui::combine_highlights( + parsed.highlights.iter().filter_map(|(range, highlight)| { + highlight + .to_highlight_style(&syntax_theme) + .map(|style| (range.clone(), style)) + }), + parsed.regions.iter().zip(&parsed.region_ranges).filter_map( + |(region, range)| { + if region.code { + Some(( + range.clone(), + HighlightStyle { + background_color: Some(code_span_bg_color), + ..Default::default() + }, + )) + } else { + None + } + }, + ), + ); + let mut links = Vec::new(); + let mut link_ranges = Vec::new(); + for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) { + if let Some(link) = region.link.clone() { + links.push(link); + link_ranges.push(range.clone()); + } + } + let workspace = workspace_clone.clone(); + let element = div() + .child( + InteractiveText::new( + element_id, + StyledText::new(parsed.contents.clone()) + .with_highlights(&text_style, highlights), + ) + .tooltip({ + let links = links.clone(); + let link_ranges = link_ranges.clone(); + move |idx, cx| { + for (ix, range) in link_ranges.iter().enumerate() { + if range.contains(&idx) { + return Some(LinkPreview::new(&links[ix].to_string(), cx)); + } + } + None + } + }) + .on_click( + link_ranges, + move |clicked_range_ix, window_cx| match &links[clicked_range_ix] { + Link::Web { url } => window_cx.open_url(url), + Link::Path { path, .. } => { + if let Some(workspace) = &workspace { + _ = workspace.update(window_cx, |workspace, cx| { + workspace + .open_abs_path(path.clone(), false, cx) + .detach(); + }); + } + } + }, + ), + ) + .into_any(); + any_element.push(element); + } + + MarkdownParagraphChunk::Image(image) => { + let (link, source_range, image_source, alt_text) = match image { + Image::Web { + link, + source_range, + url, + alt_text, + } => ( + link, + source_range, + Resource::Uri(url.clone().into()), + alt_text, + ), + Image::Path { + link, + source_range, + path, + alt_text, + .. + } => { + let image_path = Path::new(path.to_str().unwrap()); + ( + link, + source_range, + Resource::Path(Arc::from(image_path)), + alt_text, + ) + } + }; + + let element_id = cx.next_id(source_range); + + match link { + None => { + let fallback_workspace = workspace_clone.clone(); + let fallback_syntax_theme = syntax_theme.clone(); + let fallback_text_style = text_style.clone(); + let fallback_alt_text = alt_text.clone(); + let element_id_new = element_id.clone(); + let element = div() + .child(img(ImageSource::Resource(image_source)).with_fallback({ + move || { + fallback_text( + fallback_alt_text.clone().unwrap(), + element_id.clone(), + &fallback_syntax_theme, + code_span_bg_color, + fallback_workspace.clone(), + &fallback_text_style, + ) + } + })) + .id(element_id_new) + .into_any(); + any_element.push(element); + } + Some(link) => { + let link_click = link.clone(); + let link_tooltip = link.clone(); + let fallback_workspace = workspace_clone.clone(); + let fallback_syntax_theme = syntax_theme.clone(); + let fallback_text_style = text_style.clone(); + let fallback_alt_text = alt_text.clone(); + let element_id_new = element_id.clone(); + let image_element = div() + .child(img(ImageSource::Resource(image_source)).with_fallback({ + move || { + fallback_text( + fallback_alt_text.clone().unwrap(), + element_id.clone(), + &fallback_syntax_theme, + code_span_bg_color, + fallback_workspace.clone(), + &fallback_text_style, + ) + } + })) + .id(element_id_new) + .tooltip(move |cx| LinkPreview::new(&link_tooltip.to_string(), cx)) + .on_click({ + let workspace = workspace_clone.clone(); + move |_event, window_cx| match &link_click { + Link::Web { url } => window_cx.open_url(url), + Link::Path { path, .. } => { + if let Some(workspace) = &workspace { + _ = workspace.update(window_cx, |workspace, cx| { + workspace + .open_abs_path(path.clone(), false, cx) + .detach(); + }); + } + } + } + }) + .into_any(); + any_element.push(image_element); + } + } + } + } + } + + any_element +} + +fn render_markdown_rule(cx: &mut RenderContext) -> AnyElement { + let rule = div().w_full().h(px(2.)).bg(cx.border_color); + div().pt_3().pb_3().child(rule).into_any() +} + +fn fallback_text( + parsed: ParsedMarkdownText, + source_range: ElementId, + syntax_theme: &theme::SyntaxTheme, + code_span_bg_color: Hsla, + workspace: Option>, + text_style: &TextStyle, +) -> AnyElement { + let element_id = source_range; let highlights = gpui::combine_highlights( parsed.highlights.iter().filter_map(|(range, highlight)| { - let highlight = highlight.to_highlight_style(&cx.syntax_theme)?; + let highlight = highlight.to_highlight_style(syntax_theme)?; Some((range.clone(), highlight)) }), parsed @@ -421,7 +637,7 @@ fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) -> Some(( range.clone(), HighlightStyle { - background_color: Some(cx.code_span_background_color), + background_color: Some(code_span_bg_color), ..Default::default() }, )) @@ -430,7 +646,6 @@ fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) -> } }), ); - let mut links = Vec::new(); let mut link_ranges = Vec::new(); for (range, region) in parsed.region_ranges.iter().zip(&parsed.regions) { @@ -439,45 +654,38 @@ fn render_markdown_text(parsed: &ParsedMarkdownText, cx: &mut RenderContext) -> link_ranges.push(range.clone()); } } - - let workspace = cx.workspace.clone(); - - InteractiveText::new( - element_id, - StyledText::new(parsed.contents.clone()).with_highlights(&cx.text_style, highlights), - ) - .tooltip({ - let links = links.clone(); - let link_ranges = link_ranges.clone(); - move |idx, cx| { - for (ix, range) in link_ranges.iter().enumerate() { - if range.contains(&idx) { - return Some(LinkPreview::new(&links[ix].to_string(), cx)); + let element = div() + .child( + InteractiveText::new( + element_id, + StyledText::new(parsed.contents.clone()).with_highlights(text_style, highlights), + ) + .tooltip({ + let links = links.clone(); + let link_ranges = link_ranges.clone(); + move |idx, cx| { + for (ix, range) in link_ranges.iter().enumerate() { + if range.contains(&idx) { + return Some(LinkPreview::new(&links[ix].to_string(), cx)); + } + } + None } - } - None - } - }) - .on_click( - link_ranges, - move |clicked_range_ix, window_cx| match &links[clicked_range_ix] { - Link::Web { url } => window_cx.open_url(url), - Link::Path { - path, - display_path: _, - } => { - if let Some(workspace) = &workspace { - _ = workspace.update(window_cx, |workspace, cx| { - workspace.open_abs_path(path.clone(), false, cx).detach(); - }); - } - } - }, - ) - .into_any_element() -} - -fn render_markdown_rule(cx: &mut RenderContext) -> AnyElement { - let rule = div().w_full().h(px(2.)).bg(cx.border_color); - div().pt_3().pb_3().child(rule).into_any() + }) + .on_click( + link_ranges, + move |clicked_range_ix, window_cx| match &links[clicked_range_ix] { + Link::Web { url } => window_cx.open_url(url), + Link::Path { path, .. } => { + if let Some(workspace) = &workspace { + _ = workspace.update(window_cx, |workspace, cx| { + workspace.open_abs_path(path.clone(), false, cx).detach(); + }); + } + } + }, + ), + ) + .into_any(); + return element; } diff --git a/crates/notifications/src/notification_store.rs b/crates/notifications/src/notification_store.rs index 5c3de53ee1..a61f1da1c4 100644 --- a/crates/notifications/src/notification_store.rs +++ b/crates/notifications/src/notification_store.rs @@ -238,11 +238,8 @@ impl NotificationStore { ) -> Result<()> { this.update(&mut cx, |this, cx| { if let Some(notification) = envelope.payload.notification { - if let Some(rpc::Notification::ChannelMessageMention { - message_id, - sender_id: _, - channel_id: _, - }) = Notification::from_proto(¬ification) + if let Some(rpc::Notification::ChannelMessageMention { message_id, .. }) = + Notification::from_proto(¬ification) { let fetch_message_task = this.channel_store.update(cx, |this, cx| { this.fetch_channel_messages(vec![message_id], cx) diff --git a/crates/repl/src/notebook/cell.rs b/crates/repl/src/notebook/cell.rs index 055e4c09f8..12d11853fb 100644 --- a/crates/repl/src/notebook/cell.rs +++ b/crates/repl/src/notebook/cell.rs @@ -114,7 +114,7 @@ impl Cell { id, metadata, source, - attachments: _, + .. } => { let source = source.join(""); diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index 80b7786c24..df830419d3 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/crates/rich_text/src/rich_text.rs @@ -310,12 +310,7 @@ pub fn render_markdown_mut( } Event::Start(tag) => match tag { Tag::Paragraph => new_paragraph(text, &mut list_stack), - Tag::Heading { - level: _, - id: _, - classes: _, - attrs: _, - } => { + Tag::Heading { .. } => { new_paragraph(text, &mut list_stack); bold_depth += 1; } @@ -333,12 +328,7 @@ pub fn render_markdown_mut( Tag::Emphasis => italic_depth += 1, Tag::Strong => bold_depth += 1, Tag::Strikethrough => strikethrough_depth += 1, - Tag::Link { - link_type: _, - dest_url, - title: _, - id: _, - } => link_url = Some(dest_url.to_string()), + Tag::Link { dest_url, .. } => link_url = Some(dest_url.to_string()), Tag::List(number) => { list_stack.push((number, false)); } From c28f5b11f82e9e67901659462aed9ace54de6cd9 Mon Sep 17 00:00:00 2001 From: teapo <75266237+4teapo@users.noreply.github.com> Date: Fri, 22 Nov 2024 22:50:25 +0000 Subject: [PATCH 135/157] Allow overrides for json-language-server settings (#20748) Closes #20739 The JSON LSP adapter now merges user settings with cached settings, and util::merge_json_value_into pushes array contents from source to target. --- crates/languages/src/json.rs | 21 ++++++++++++++++----- crates/util/src/util.rs | 6 ++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index c0c7e6f453..9c6315d2e2 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -9,7 +9,7 @@ use http_client::github::{latest_github_release, GitHubLspBinaryVersion}; use language::{LanguageRegistry, LanguageToolchainStore, LspAdapter, LspAdapterDelegate}; use lsp::{LanguageServerBinary, LanguageServerName}; use node_runtime::NodeRuntime; -use project::ContextProviderWithTasks; +use project::{lsp_store::language_server_settings, ContextProviderWithTasks}; use serde_json::{json, Value}; use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore}; use smol::{ @@ -25,7 +25,7 @@ use std::{ sync::{Arc, OnceLock}, }; use task::{TaskTemplate, TaskTemplates, VariableName}; -use util::{fs::remove_matching, maybe, ResultExt}; +use util::{fs::remove_matching, maybe, merge_json_value_into, ResultExt}; const SERVER_PATH: &str = "node_modules/vscode-langservers-extracted/bin/vscode-json-language-server"; @@ -194,15 +194,26 @@ impl LspAdapter for JsonLspAdapter { async fn workspace_configuration( self: Arc, - _: &Arc, + delegate: &Arc, _: Arc, cx: &mut AsyncAppContext, ) -> Result { - cx.update(|cx| { + let mut config = cx.update(|cx| { self.workspace_config .get_or_init(|| Self::get_workspace_config(self.languages.language_names(), cx)) .clone() - }) + })?; + + let project_options = cx.update(|cx| { + language_server_settings(delegate.as_ref(), &self.name(), cx) + .and_then(|s| s.settings.clone()) + })?; + + if let Some(override_options) = project_options { + merge_json_value_into(override_options, &mut config); + } + + Ok(config) } fn language_ids(&self) -> HashMap { diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 5141f85797..fe3f7ef9a0 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -149,6 +149,12 @@ pub fn merge_json_value_into(source: serde_json::Value, target: &mut serde_json: } } + (Value::Array(source), Value::Array(target)) => { + for value in source { + target.push(value); + } + } + (source, target) => *target = source, } } From 8240a52a392cc920fe6d545b5f2a0d4b90df4697 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 22 Nov 2024 14:59:40 -0800 Subject: [PATCH 136/157] Prevent panels from being resized past the edge of the workspace (#20637) Closes #20593 Release Notes: - Fixed a bug where it is possible to get in near-unrecoverable panel state by resizing the panel past the edge of the workspace. Co-authored-by: Trace --- crates/workspace/src/dock.rs | 12 +++- crates/workspace/src/workspace.rs | 97 ++++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 30 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 30ab109879..acc47cd11e 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use ui::{h_flex, ContextMenu, IconButton, Tooltip}; use ui::{prelude::*, right_click_menu}; -const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.); +pub(crate) const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.); pub enum PanelEvent { ZoomIn, @@ -574,6 +574,7 @@ impl Dock { pub fn resize_active_panel(&mut self, size: Option, cx: &mut ViewContext) { if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) { let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE).round()); + entry.panel.set_size(size, cx); cx.notify(); } @@ -593,6 +594,15 @@ impl Dock { dispatch_context } + + pub fn clamp_panel_size(&mut self, max_size: Pixels, cx: &mut WindowContext) { + let max_size = px((max_size.0 - RESIZE_HANDLE_SIZE.0).abs()); + for panel in self.panel_entries.iter().map(|entry| &entry.panel) { + if panel.size(cx) > max_size { + panel.set_size(Some(max_size.max(RESIZE_HANDLE_SIZE)), cx); + } + } + } } impl Render for Dock { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 45de781577..42db3183bd 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -21,7 +21,7 @@ use client::{ }; use collections::{hash_map, HashMap, HashSet}; use derive_more::{Deref, DerefMut}; -use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; +use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle, RESIZE_HANDLE_SIZE}; use futures::{ channel::{ mpsc::{self, UnboundedReceiver, UnboundedSender}, @@ -4824,7 +4824,27 @@ impl Render for Workspace { let this = cx.view().clone(); canvas( move |bounds, cx| { - this.update(cx, |this, _cx| this.bounds = bounds) + this.update(cx, |this, cx| { + let bounds_changed = this.bounds != bounds; + this.bounds = bounds; + + if bounds_changed { + this.left_dock.update(cx, |dock, cx| { + dock.clamp_panel_size(bounds.size.width, cx) + }); + + this.right_dock.update(cx, |dock, cx| { + dock.clamp_panel_size(bounds.size.width, cx) + }); + + this.bottom_dock.update(cx, |dock, cx| { + dock.clamp_panel_size( + bounds.size.height, + cx, + ) + }); + } + }) }, |_, _, _| {}, ) @@ -4836,42 +4856,27 @@ impl Render for Workspace { |workspace, e: &DragMoveEvent, cx| { match e.drag(cx).0 { DockPosition::Left => { - let size = e.event.position.x - - workspace.bounds.left(); - workspace.left_dock.update( + resize_left_dock( + e.event.position.x + - workspace.bounds.left(), + workspace, cx, - |left_dock, cx| { - left_dock.resize_active_panel( - Some(size), - cx, - ); - }, ); } DockPosition::Right => { - let size = workspace.bounds.right() - - e.event.position.x; - workspace.right_dock.update( + resize_right_dock( + workspace.bounds.right() + - e.event.position.x, + workspace, cx, - |right_dock, cx| { - right_dock.resize_active_panel( - Some(size), - cx, - ); - }, ); } DockPosition::Bottom => { - let size = workspace.bounds.bottom() - - e.event.position.y; - workspace.bottom_dock.update( + resize_bottom_dock( + workspace.bounds.bottom() + - e.event.position.y, + workspace, cx, - |bottom_dock, cx| { - bottom_dock.resize_active_panel( - Some(size), - cx, - ); - }, ); } } @@ -4959,6 +4964,40 @@ impl Render for Workspace { } } +fn resize_bottom_dock( + new_size: Pixels, + workspace: &mut Workspace, + cx: &mut ViewContext<'_, Workspace>, +) { + let size = new_size.min(workspace.bounds.bottom() - RESIZE_HANDLE_SIZE); + workspace.bottom_dock.update(cx, |bottom_dock, cx| { + bottom_dock.resize_active_panel(Some(size), cx); + }); +} + +fn resize_right_dock( + new_size: Pixels, + workspace: &mut Workspace, + cx: &mut ViewContext<'_, Workspace>, +) { + let size = new_size.max(workspace.bounds.left() - RESIZE_HANDLE_SIZE); + workspace.right_dock.update(cx, |right_dock, cx| { + right_dock.resize_active_panel(Some(size), cx); + }); +} + +fn resize_left_dock( + new_size: Pixels, + workspace: &mut Workspace, + cx: &mut ViewContext<'_, Workspace>, +) { + let size = new_size.min(workspace.bounds.right() - RESIZE_HANDLE_SIZE); + + workspace.left_dock.update(cx, |left_dock, cx| { + left_dock.resize_active_panel(Some(size), cx); + }); +} + impl WorkspaceStore { pub fn new(client: Arc, cx: &mut ModelContext) -> Self { Self { From c9f2c2792c365577daedae5586a3e047dbcd338e Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Fri, 22 Nov 2024 16:03:46 -0700 Subject: [PATCH 137/157] Improve error handling and resource cleanup in `linux/x11/window.rs` (#21079) * Fixes registration of event handler for xinput-2 device changes, revealed by this improvement. * Pushes `.unwrap()` panic-ing outwards to callers. * Includes a description of what the X11 call was doing when a failure was encountered. * Fixes a variety of places where the X11 reply wasn't being inspected for failures. * Destroys windows on failure during setup. New structure makes it possible for the caller of `open_window` to carry on despite failures, and so partially initialized window should be removed (though all calls I looked at also panic currently). Considered pushing this through `linux/x11/client.rs` too but figured it'd be nice to minimize merge conflicts with #20853. Release Notes: - N/A --- crates/gpui/src/platform/linux/x11/client.rs | 20 +- crates/gpui/src/platform/linux/x11/display.rs | 13 +- crates/gpui/src/platform/linux/x11/window.rs | 770 ++++++++++-------- 3 files changed, 454 insertions(+), 349 deletions(-) diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index f6c3af0348..1fd0e9aa66 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -776,11 +776,11 @@ impl X11Client { }, }; let window = self.get_window(event.window)?; - window.configure(bounds); + window.configure(bounds).unwrap(); } Event::PropertyNotify(event) => { let window = self.get_window(event.window)?; - window.property_notify(event); + window.property_notify(event).unwrap(); } Event::FocusIn(event) => { let window = self.get_window(event.event)?; @@ -1258,11 +1258,9 @@ impl LinuxClient for X11Client { .iter() .enumerate() .filter_map(|(root_id, _)| { - Some(Rc::new(X11Display::new( - &state.xcb_connection, - state.scale_factor, - root_id, - )?) as Rc) + Some(Rc::new( + X11Display::new(&state.xcb_connection, state.scale_factor, root_id).ok()?, + ) as Rc) }) .collect() } @@ -1283,11 +1281,9 @@ impl LinuxClient for X11Client { fn display(&self, id: DisplayId) -> Option> { let state = self.0.borrow(); - Some(Rc::new(X11Display::new( - &state.xcb_connection, - state.scale_factor, - id.0 as usize, - )?)) + Some(Rc::new( + X11Display::new(&state.xcb_connection, state.scale_factor, id.0 as usize).ok()?, + )) } fn open_window( diff --git a/crates/gpui/src/platform/linux/x11/display.rs b/crates/gpui/src/platform/linux/x11/display.rs index 871d709fa9..4983e2f5a3 100644 --- a/crates/gpui/src/platform/linux/x11/display.rs +++ b/crates/gpui/src/platform/linux/x11/display.rs @@ -13,12 +13,17 @@ pub(crate) struct X11Display { impl X11Display { pub(crate) fn new( - xc: &XCBConnection, + xcb: &XCBConnection, scale_factor: f32, x_screen_index: usize, - ) -> Option { - let screen = xc.setup().roots.get(x_screen_index).unwrap(); - Some(Self { + ) -> anyhow::Result { + let Some(screen) = xcb.setup().roots.get(x_screen_index) else { + return Err(anyhow::anyhow!( + "No screen found with index {}", + x_screen_index + )); + }; + Ok(Self { x_screen_index, bounds: Bounds { origin: Default::default(), diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index 4df1b50f3f..ae9abe7146 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -1,4 +1,4 @@ -use anyhow::Context; +use anyhow::{anyhow, Context}; use crate::{ platform::blade::{BladeRenderer, BladeSurfaceConfig}, @@ -14,6 +14,8 @@ use raw_window_handle as rwh; use util::{maybe, ResultExt}; use x11rb::{ connection::Connection, + cookie::{Cookie, VoidCookie}, + errors::ConnectionError, properties::WmSizeHints, protocol::{ sync, @@ -25,7 +27,7 @@ use x11rb::{ }; use std::{ - cell::RefCell, ffi::c_void, mem::size_of, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc, + cell::RefCell, ffi::c_void, fmt::Display, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc, sync::Arc, }; @@ -77,17 +79,16 @@ x11rb::atom_manager! { } } -fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window) -> gpu::Extent { - let reply = xcb_connection - .get_geometry(x_window) - .unwrap() - .reply() - .unwrap(); - gpu::Extent { +fn query_render_extent( + xcb: &Rc, + x_window: xproto::Window, +) -> anyhow::Result { + let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?; + Ok(gpu::Extent { width: reply.width as u32, height: reply.height as u32, depth: 1, - } + }) } impl ResizeEdge { @@ -148,7 +149,7 @@ impl EdgeConstraints { } } -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] struct Visual { id: xproto::Visualid, colormap: u32, @@ -163,8 +164,8 @@ struct VisualSet { black_pixel: u32, } -fn find_visuals(xcb_connection: &XCBConnection, screen_index: usize) -> VisualSet { - let screen = &xcb_connection.setup().roots[screen_index]; +fn find_visuals(xcb: &XCBConnection, screen_index: usize) -> VisualSet { + let screen = &xcb.setup().roots[screen_index]; let mut set = VisualSet { inherit: Visual { id: screen.root_visual, @@ -277,13 +278,16 @@ impl X11WindowState { pub(crate) struct X11WindowStatePtr { pub state: Rc>, pub(crate) callbacks: Rc>, - xcb_connection: Rc, + xcb: Rc, x_window: xproto::Window, } impl rwh::HasWindowHandle for RawWindow { fn window_handle(&self) -> Result { - let non_zero = NonZeroU32::new(self.window_id).unwrap(); + let Some(non_zero) = NonZeroU32::new(self.window_id) else { + log::error!("RawWindow.window_id zero when getting window handle."); + return Err(rwh::HandleError::Unavailable); + }; let mut handle = rwh::XcbWindowHandle::new(non_zero); handle.visual_id = NonZeroU32::new(self.visual_id); Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) }) @@ -291,7 +295,10 @@ impl rwh::HasWindowHandle for RawWindow { } impl rwh::HasDisplayHandle for RawWindow { fn display_handle(&self) -> Result { - let non_zero = NonNull::new(self.connection).unwrap(); + let Some(non_zero) = NonNull::new(self.connection) else { + log::error!("Null RawWindow.connection when getting display handle."); + return Err(rwh::HandleError::Unavailable); + }; let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32); Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) }) } @@ -308,6 +315,43 @@ impl rwh::HasDisplayHandle for X11Window { } } +fn check_reply( + failure_context: F, + result: Result>, ConnectionError>, +) -> anyhow::Result<()> +where + C: Display + Send + Sync + 'static, + F: FnOnce() -> C, +{ + result + .map_err(|connection_error| anyhow!(connection_error)) + .and_then(|response| { + response + .check() + .map_err(|error_response| anyhow!(error_response)) + }) + .with_context(failure_context) +} + +fn get_reply( + failure_context: F, + result: Result, O>, ConnectionError>, +) -> anyhow::Result +where + C: Display + Send + Sync + 'static, + F: FnOnce() -> C, + O: x11rb::x11_utils::TryParse, +{ + result + .map_err(|connection_error| anyhow!(connection_error)) + .and_then(|response| { + response + .reply() + .map_err(|error_response| anyhow!(error_response)) + }) + .with_context(failure_context) +} + impl X11WindowState { #[allow(clippy::too_many_arguments)] pub fn new( @@ -315,7 +359,7 @@ impl X11WindowState { client: X11ClientStatePtr, executor: ForegroundExecutor, params: WindowParams, - xcb_connection: &Rc, + xcb: &Rc, client_side_decorations_supported: bool, x_main_screen_index: usize, x_window: xproto::Window, @@ -327,7 +371,7 @@ impl X11WindowState { .display_id .map_or(x_main_screen_index, |did| did.0 as usize); - let visual_set = find_visuals(&xcb_connection, x_screen_index); + let visual_set = find_visuals(&xcb, x_screen_index); let visual = match visual_set.transparent { Some(visual) => visual, @@ -341,12 +385,12 @@ impl X11WindowState { let colormap = if visual.colormap != 0 { visual.colormap } else { - let id = xcb_connection.generate_id().unwrap(); + let id = xcb.generate_id()?; log::info!("Creating colormap {}", id); - xcb_connection - .create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id) - .unwrap() - .check()?; + check_reply( + || format!("X11 CreateColormap failed. id: {}", id), + xcb.create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id), + )?; id }; @@ -370,8 +414,12 @@ impl X11WindowState { bounds.size.height = 600.into(); } - xcb_connection - .create_window( + check_reply( + || { + format!("X11 CreateWindow failed. depth: {}, x_window: {}, visual_set.root: {}, bounds.origin.x.0: {}, bounds.origin.y.0: {}, bounds.size.width.0: {}, bounds.size.height.0: {}", + visual.depth, x_window, visual_set.root, bounds.origin.x.0 + 2, bounds.origin.y.0, bounds.size.width.0, bounds.size.height.0) + }, + xcb.create_window( visual.depth, x_window, visual_set.root, @@ -383,189 +431,205 @@ impl X11WindowState { xproto::WindowClass::INPUT_OUTPUT, visual.id, &win_aux, - ) - .unwrap() - .check().with_context(|| { - format!("CreateWindow request to X server failed. depth: {}, x_window: {}, visual_set.root: {}, bounds.origin.x.0: {}, bounds.origin.y.0: {}, bounds.size.width.0: {}, bounds.size.height.0: {}", - visual.depth, x_window, visual_set.root, bounds.origin.x.0 + 2, bounds.origin.y.0, bounds.size.width.0, bounds.size.height.0) - })?; + ), + )?; - if let Some(size) = params.window_min_size { - let mut size_hints = WmSizeHints::new(); - size_hints.min_size = Some((size.width.0 as i32, size.height.0 as i32)); - size_hints - .set_normal_hints(xcb_connection, x_window) - .unwrap(); - } + // Collect errors during setup, so that window can be destroyed on failure. + let setup_result = maybe!({ + if let Some(size) = params.window_min_size { + let mut size_hints = WmSizeHints::new(); + let min_size = (size.width.0 as i32, size.height.0 as i32); + size_hints.min_size = Some(min_size); + check_reply( + || { + format!( + "X11 change of WM_SIZE_HINTS failed. min_size: {:?}", + min_size + ) + }, + size_hints.set_normal_hints(xcb, x_window), + )?; + } - let reply = xcb_connection - .get_geometry(x_window) - .unwrap() - .reply() - .unwrap(); - if reply.x == 0 && reply.y == 0 { - bounds.origin.x.0 += 2; - // Work around a bug where our rendered content appears - // outside the window bounds when opened at the default position - // (14px, 49px on X + Gnome + Ubuntu 22). - xcb_connection - .configure_window( - x_window, - &xproto::ConfigureWindowAux::new() - .x(bounds.origin.x.0) - .y(bounds.origin.y.0), - ) - .unwrap(); - } - if let Some(titlebar) = params.titlebar { - if let Some(title) = titlebar.title { - xcb_connection - .change_property8( + let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?; + if reply.x == 0 && reply.y == 0 { + bounds.origin.x.0 += 2; + // Work around a bug where our rendered content appears + // outside the window bounds when opened at the default position + // (14px, 49px on X + Gnome + Ubuntu 22). + let x = bounds.origin.x.0; + let y = bounds.origin.y.0; + check_reply( + || format!("X11 ConfigureWindow failed. x: {}, y: {}", x, y), + xcb.configure_window(x_window, &xproto::ConfigureWindowAux::new().x(x).y(y)), + )?; + } + if let Some(titlebar) = params.titlebar { + if let Some(title) = titlebar.title { + check_reply( + || "X11 ChangeProperty8 on window title failed.", + xcb.change_property8( + xproto::PropMode::REPLACE, + x_window, + xproto::AtomEnum::WM_NAME, + xproto::AtomEnum::STRING, + title.as_bytes(), + ), + )?; + } + } + if params.kind == WindowKind::PopUp { + check_reply( + || "X11 ChangeProperty32 setting window type for pop-up failed.", + xcb.change_property32( xproto::PropMode::REPLACE, x_window, - xproto::AtomEnum::WM_NAME, - xproto::AtomEnum::STRING, - title.as_bytes(), - ) - .unwrap(); + atoms._NET_WM_WINDOW_TYPE, + xproto::AtomEnum::ATOM, + &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION], + ), + )?; } - } - if params.kind == WindowKind::PopUp { - xcb_connection - .change_property32( + + check_reply( + || "X11 ChangeProperty32 setting protocols failed.", + xcb.change_property32( xproto::PropMode::REPLACE, x_window, - atoms._NET_WM_WINDOW_TYPE, + atoms.WM_PROTOCOLS, xproto::AtomEnum::ATOM, - &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION], - ) - .unwrap(); + &[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST], + ), + )?; + + get_reply( + || "X11 sync protocol initialize failed.", + sync::initialize(xcb, 3, 1), + )?; + let sync_request_counter = xcb.generate_id()?; + check_reply( + || "X11 sync CreateCounter failed.", + sync::create_counter(xcb, sync_request_counter, sync::Int64 { lo: 0, hi: 0 }), + )?; + + check_reply( + || "X11 ChangeProperty32 setting sync request counter failed.", + xcb.change_property32( + xproto::PropMode::REPLACE, + x_window, + atoms._NET_WM_SYNC_REQUEST_COUNTER, + xproto::AtomEnum::CARDINAL, + &[sync_request_counter], + ), + )?; + + check_reply( + || "X11 XiSelectEvents failed.", + xcb.xinput_xi_select_events( + x_window, + &[xinput::EventMask { + deviceid: XINPUT_ALL_DEVICE_GROUPS, + mask: vec![ + xinput::XIEventMask::MOTION + | xinput::XIEventMask::BUTTON_PRESS + | xinput::XIEventMask::BUTTON_RELEASE + | xinput::XIEventMask::ENTER + | xinput::XIEventMask::LEAVE, + ], + }], + ), + )?; + + check_reply( + || "X11 XiSelectEvents for device changes failed.", + xcb.xinput_xi_select_events( + x_window, + &[xinput::EventMask { + deviceid: XINPUT_ALL_DEVICES, + mask: vec![ + xinput::XIEventMask::HIERARCHY | xinput::XIEventMask::DEVICE_CHANGED, + ], + }], + ), + )?; + + xcb.flush().with_context(|| "X11 Flush failed.")?; + + let raw = RawWindow { + connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(xcb) + as *mut _, + screen_id: x_screen_index, + window_id: x_window, + visual_id: visual.id, + }; + let gpu = Arc::new( + unsafe { + gpu::Context::init_windowed( + &raw, + gpu::ContextDesc { + validation: false, + capture: false, + overlay: false, + }, + ) + } + .map_err(|e| anyhow!("{:?}", e))?, + ); + + let config = BladeSurfaceConfig { + // Note: this has to be done after the GPU init, or otherwise + // the sizes are immediately invalidated. + size: query_render_extent(xcb, x_window)?, + // We set it to transparent by default, even if we have client-side + // decorations, since those seem to work on X11 even without `true` here. + // If the window appearance changes, then the renderer will get updated + // too + transparent: false, + }; + check_reply(|| "X11 MapWindow failed.", xcb.map_window(x_window))?; + + let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?); + + Ok(Self { + client, + executor, + display, + _raw: raw, + x_root_window: visual_set.root, + bounds: bounds.to_pixels(scale_factor), + scale_factor, + renderer: BladeRenderer::new(gpu, config), + atoms: *atoms, + input_handler: None, + active: false, + hovered: false, + fullscreen: false, + maximized_vertical: false, + maximized_horizontal: false, + hidden: false, + appearance, + handle, + background_appearance: WindowBackgroundAppearance::Opaque, + destroyed: false, + client_side_decorations_supported, + decorations: WindowDecorations::Server, + last_insets: [0, 0, 0, 0], + edge_constraints: None, + counter_id: sync_request_counter, + last_sync_counter: None, + }) + }); + + if setup_result.is_err() { + check_reply( + || "X11 DestroyWindow failed while cleaning it up after setup failure.", + xcb.destroy_window(x_window), + )?; + xcb.flush() + .with_context(|| "X11 Flush failed while cleaning it up after setup failure.")?; } - xcb_connection - .change_property32( - xproto::PropMode::REPLACE, - x_window, - atoms.WM_PROTOCOLS, - xproto::AtomEnum::ATOM, - &[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST], - ) - .unwrap(); - - sync::initialize(xcb_connection, 3, 1).unwrap(); - let sync_request_counter = xcb_connection.generate_id().unwrap(); - sync::create_counter( - xcb_connection, - sync_request_counter, - sync::Int64 { lo: 0, hi: 0 }, - ) - .unwrap(); - - xcb_connection - .change_property32( - xproto::PropMode::REPLACE, - x_window, - atoms._NET_WM_SYNC_REQUEST_COUNTER, - xproto::AtomEnum::CARDINAL, - &[sync_request_counter], - ) - .unwrap(); - - xcb_connection - .xinput_xi_select_events( - x_window, - &[xinput::EventMask { - deviceid: XINPUT_ALL_DEVICE_GROUPS, - mask: vec![ - xinput::XIEventMask::MOTION - | xinput::XIEventMask::BUTTON_PRESS - | xinput::XIEventMask::BUTTON_RELEASE - | xinput::XIEventMask::ENTER - | xinput::XIEventMask::LEAVE, - ], - }], - ) - .unwrap(); - - xcb_connection - .xinput_xi_select_events( - x_window, - &[xinput::EventMask { - deviceid: XINPUT_ALL_DEVICES, - mask: vec![ - xinput::XIEventMask::HIERARCHY, - xinput::XIEventMask::DEVICE_CHANGED, - ], - }], - ) - .unwrap(); - - xcb_connection.flush().unwrap(); - - let raw = RawWindow { - connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection( - xcb_connection, - ) as *mut _, - screen_id: x_screen_index, - window_id: x_window, - visual_id: visual.id, - }; - let gpu = Arc::new( - unsafe { - gpu::Context::init_windowed( - &raw, - gpu::ContextDesc { - validation: false, - capture: false, - overlay: false, - }, - ) - } - .map_err(|e| anyhow::anyhow!("{:?}", e))?, - ); - - let config = BladeSurfaceConfig { - // Note: this has to be done after the GPU init, or otherwise - // the sizes are immediately invalidated. - size: query_render_extent(xcb_connection, x_window), - // We set it to transparent by default, even if we have client-side - // decorations, since those seem to work on X11 even without `true` here. - // If the window appearance changes, then the renderer will get updated - // too - transparent: false, - }; - xcb_connection.map_window(x_window).unwrap(); - - Ok(Self { - client, - executor, - display: Rc::new( - X11Display::new(xcb_connection, scale_factor, x_screen_index).unwrap(), - ), - _raw: raw, - x_root_window: visual_set.root, - bounds: bounds.to_pixels(scale_factor), - scale_factor, - renderer: BladeRenderer::new(gpu, config), - atoms: *atoms, - input_handler: None, - active: false, - hovered: false, - fullscreen: false, - maximized_vertical: false, - maximized_horizontal: false, - hidden: false, - appearance, - handle, - background_appearance: WindowBackgroundAppearance::Opaque, - destroyed: false, - client_side_decorations_supported, - decorations: WindowDecorations::Server, - last_insets: [0, 0, 0, 0], - edge_constraints: None, - counter_id: sync_request_counter, - last_sync_counter: None, - }) + setup_result } fn content_size(&self) -> Size { @@ -577,6 +641,28 @@ impl X11WindowState { } } +/// A handle to an X11 window which destroys it on Drop. +pub struct X11WindowHandle { + id: xproto::Window, + xcb: Rc, +} + +impl Drop for X11WindowHandle { + fn drop(&mut self) { + maybe!({ + check_reply( + || "X11 DestroyWindow failed while dropping X11WindowHandle.", + self.xcb.destroy_window(self.id), + )?; + self.xcb + .flush() + .with_context(|| "X11 Flush failed while dropping X11WindowHandle.")?; + anyhow::Ok(()) + }) + .log_err(); + } +} + pub(crate) struct X11Window(pub X11WindowStatePtr); impl Drop for X11Window { @@ -585,13 +671,17 @@ impl Drop for X11Window { state.renderer.destroy(); let destroy_x_window = maybe!({ - self.0.xcb_connection.unmap_window(self.0.x_window)?; - self.0.xcb_connection.destroy_window(self.0.x_window)?; - self.0.xcb_connection.flush()?; + check_reply( + || "X11 DestroyWindow failure.", + self.0.xcb.destroy_window(self.0.x_window), + )?; + self.0 + .xcb + .flush() + .with_context(|| "X11 Flush failed after calling DestroyWindow.")?; anyhow::Ok(()) }) - .context("unmapping and destroying X11 window") .log_err(); if destroy_x_window.is_some() { @@ -627,7 +717,7 @@ impl X11Window { client: X11ClientStatePtr, executor: ForegroundExecutor, params: WindowParams, - xcb_connection: &Rc, + xcb: &Rc, client_side_decorations_supported: bool, x_main_screen_index: usize, x_window: xproto::Window, @@ -641,7 +731,7 @@ impl X11Window { client, executor, params, - xcb_connection, + xcb, client_side_decorations_supported, x_main_screen_index, x_window, @@ -650,17 +740,23 @@ impl X11Window { appearance, )?)), callbacks: Rc::new(RefCell::new(Callbacks::default())), - xcb_connection: xcb_connection.clone(), + xcb: xcb.clone(), x_window, }; let state = ptr.state.borrow_mut(); - ptr.set_wm_properties(state); + ptr.set_wm_properties(state)?; Ok(Self(ptr)) } - fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) { + fn set_wm_hints C>( + &self, + failure_context: F, + wm_hint_property_state: WmHintPropertyState, + prop1: u32, + prop2: u32, + ) -> anyhow::Result<()> { let state = self.0.state.borrow(); let message = ClientMessageEvent::new( 32, @@ -668,51 +764,45 @@ impl X11Window { state.atoms._NET_WM_STATE, [wm_hint_property_state as u32, prop1, prop2, 1, 0], ); - self.0 - .xcb_connection - .send_event( + check_reply( + failure_context, + self.0.xcb.send_event( false, state.x_root_window, EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, message, - ) - .unwrap() - .check() - .unwrap(); + ), + ) } - fn get_root_position(&self, position: Point) -> TranslateCoordinatesReply { + fn get_root_position( + &self, + position: Point, + ) -> anyhow::Result { let state = self.0.state.borrow(); - self.0 - .xcb_connection - .translate_coordinates( + get_reply( + || "X11 TranslateCoordinates failed.", + self.0.xcb.translate_coordinates( self.0.x_window, state.x_root_window, (position.x.0 * state.scale_factor) as i16, (position.y.0 * state.scale_factor) as i16, - ) - .unwrap() - .reply() - .unwrap() + ), + ) } - fn send_moveresize(&self, flag: u32) { + fn send_moveresize(&self, flag: u32) -> anyhow::Result<()> { let state = self.0.state.borrow(); - self.0 - .xcb_connection - .ungrab_pointer(x11rb::CURRENT_TIME) - .unwrap() - .check() - .unwrap(); + check_reply( + || "X11 UngrabPointer before move/resize of window ailed.", + self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME), + )?; - let pointer = self - .0 - .xcb_connection - .query_pointer(self.0.x_window) - .unwrap() - .reply() - .unwrap(); + let pointer = get_reply( + || "X11 QueryPointer before move/resize of window failed.", + self.0.xcb.query_pointer(self.0.x_window), + )?; let message = ClientMessageEvent::new( 32, self.0.x_window, @@ -725,17 +815,21 @@ impl X11Window { 0, ], ); - self.0 - .xcb_connection - .send_event( + check_reply( + || "X11 SendEvent to move/resize window failed.", + self.0.xcb.send_event( false, state.x_root_window, EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, message, - ) - .unwrap(); + ), + )?; - self.0.xcb_connection.flush().unwrap(); + self.flush() + } + + fn flush(&self) -> anyhow::Result<()> { + self.0.xcb.flush().with_context(|| "X11 Flush failed.") } } @@ -751,51 +845,56 @@ impl X11WindowStatePtr { } } - pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) { + pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) -> anyhow::Result<()> { let mut state = self.state.borrow_mut(); if event.atom == state.atoms._NET_WM_STATE { - self.set_wm_properties(state); + self.set_wm_properties(state)?; } else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS { - self.set_edge_constraints(state); + self.set_edge_constraints(state)?; } + Ok(()) } - fn set_edge_constraints(&self, mut state: std::cell::RefMut) { - let reply = self - .xcb_connection - .get_property( + fn set_edge_constraints( + &self, + mut state: std::cell::RefMut, + ) -> anyhow::Result<()> { + let reply = get_reply( + || "X11 GetProperty for _GTK_EDGE_CONSTRAINTS failed.", + self.xcb.get_property( false, self.x_window, state.atoms._GTK_EDGE_CONSTRAINTS, xproto::AtomEnum::CARDINAL, 0, 4, - ) - .unwrap() - .reply() - .unwrap(); + ), + )?; if reply.value_len != 0 { let atom = u32::from_ne_bytes(reply.value[0..4].try_into().unwrap()); let edge_constraints = EdgeConstraints::from_atom(atom); state.edge_constraints.replace(edge_constraints); } + + Ok(()) } - fn set_wm_properties(&self, mut state: std::cell::RefMut) { - let reply = self - .xcb_connection - .get_property( + fn set_wm_properties( + &self, + mut state: std::cell::RefMut, + ) -> anyhow::Result<()> { + let reply = get_reply( + || "X11 GetProperty for _NET_WM_STATE failed.", + self.xcb.get_property( false, self.x_window, state.atoms._NET_WM_STATE, xproto::AtomEnum::ATOM, 0, u32::MAX, - ) - .unwrap() - .reply() - .unwrap(); + ), + )?; let atoms = reply .value @@ -821,6 +920,8 @@ impl X11WindowStatePtr { state.hidden = true; } } + + Ok(()) } pub fn close(&self) { @@ -912,7 +1013,7 @@ impl X11WindowStatePtr { bounds } - pub fn configure(&self, bounds: Bounds) { + pub fn configure(&self, bounds: Bounds) -> anyhow::Result<()> { let mut resize_args = None; let is_resize; { @@ -930,7 +1031,7 @@ impl X11WindowStatePtr { state.bounds = bounds; } - let gpu_size = query_render_extent(&self.xcb_connection, self.x_window); + let gpu_size = query_render_extent(&self.xcb, self.x_window)?; if true { state.renderer.update_drawable_size(size( DevicePixels(gpu_size.width as i32), @@ -939,7 +1040,10 @@ impl X11WindowStatePtr { resize_args = Some((state.content_size(), state.scale_factor)); } if let Some(value) = state.last_sync_counter.take() { - sync::set_counter(&self.xcb_connection, state.counter_id, value).unwrap(); + check_reply( + || "X11 sync SetCounter failed.", + sync::set_counter(&self.xcb, state.counter_id, value), + )?; } } @@ -951,9 +1055,11 @@ impl X11WindowStatePtr { } if !is_resize { if let Some(ref mut fun) = callbacks.moved { - fun() + fun(); } } + + Ok(()) } pub fn set_active(&self, focus: bool) { @@ -1025,13 +1131,11 @@ impl PlatformWindow for X11Window { } fn mouse_position(&self) -> Point { - let reply = self - .0 - .xcb_connection - .query_pointer(self.0.x_window) - .unwrap() - .reply() - .unwrap(); + let reply = get_reply( + || "X11 QueryPointer failed.", + self.0.xcb.query_pointer(self.0.x_window), + ) + .unwrap(); Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into()) } @@ -1073,7 +1177,7 @@ impl PlatformWindow for X11Window { data, ); self.0 - .xcb_connection + .xcb .send_event( false, self.0.state.borrow().x_root_window, @@ -1082,14 +1186,14 @@ impl PlatformWindow for X11Window { ) .log_err(); self.0 - .xcb_connection + .xcb .set_input_focus( xproto::InputFocus::POINTER_ROOT, self.0.x_window, xproto::Time::CURRENT_TIME, ) .log_err(); - self.0.xcb_connection.flush().unwrap(); + self.flush().unwrap(); } fn is_active(&self) -> bool { @@ -1101,28 +1205,30 @@ impl PlatformWindow for X11Window { } fn set_title(&mut self, title: &str) { - self.0 - .xcb_connection - .change_property8( + check_reply( + || "X11 ChangeProperty8 on WM_NAME failed.", + self.0.xcb.change_property8( xproto::PropMode::REPLACE, self.0.x_window, xproto::AtomEnum::WM_NAME, xproto::AtomEnum::STRING, title.as_bytes(), - ) - .unwrap(); + ), + ) + .unwrap(); - self.0 - .xcb_connection - .change_property8( + check_reply( + || "X11 ChangeProperty8 on _NET_WM_NAME failed.", + self.0.xcb.change_property8( xproto::PropMode::REPLACE, self.0.x_window, self.0.state.borrow().atoms._NET_WM_NAME, self.0.state.borrow().atoms.UTF8_STRING, title.as_bytes(), - ) - .unwrap(); - self.0.xcb_connection.flush().unwrap(); + ), + ) + .unwrap(); + self.flush().unwrap(); } fn set_app_id(&mut self, app_id: &str) { @@ -1131,18 +1237,17 @@ impl PlatformWindow for X11Window { data.push(b'\0'); data.extend(app_id.bytes()); // class - self.0 - .xcb_connection - .change_property8( + check_reply( + || "X11 ChangeProperty8 for WM_CLASS failed.", + self.0.xcb.change_property8( xproto::PropMode::REPLACE, self.0.x_window, xproto::AtomEnum::WM_CLASS, xproto::AtomEnum::STRING, &data, - ) - .unwrap() - .check() - .unwrap(); + ), + ) + .unwrap(); } fn set_edited(&mut self, _edited: bool) { @@ -1169,35 +1274,38 @@ impl PlatformWindow for X11Window { state.atoms.WM_CHANGE_STATE, [WINDOW_ICONIC_STATE, 0, 0, 0, 0], ); - self.0 - .xcb_connection - .send_event( + check_reply( + || "X11 SendEvent to minimize window failed.", + self.0.xcb.send_event( false, state.x_root_window, EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, message, - ) - .unwrap() - .check() - .unwrap(); + ), + ) + .unwrap(); } fn zoom(&self) { let state = self.0.state.borrow(); self.set_wm_hints( + || "X11 SendEvent to maximize a window failed.", WmHintPropertyState::Toggle, state.atoms._NET_WM_STATE_MAXIMIZED_VERT, state.atoms._NET_WM_STATE_MAXIMIZED_HORZ, - ); + ) + .unwrap(); } fn toggle_fullscreen(&self) { let state = self.0.state.borrow(); self.set_wm_hints( + || "X11 SendEvent to fullscreen a window failed.", WmHintPropertyState::Toggle, state.atoms._NET_WM_STATE_FULLSCREEN, xproto::AtomEnum::NONE.into(), - ); + ) + .unwrap(); } fn is_fullscreen(&self) -> bool { @@ -1253,14 +1361,13 @@ impl PlatformWindow for X11Window { fn show_window_menu(&self, position: Point) { let state = self.0.state.borrow(); - self.0 - .xcb_connection - .ungrab_pointer(x11rb::CURRENT_TIME) - .unwrap() - .check() - .unwrap(); + check_reply( + || "X11 UngrabPointer failed.", + self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME), + ) + .unwrap(); - let coords = self.get_root_position(position); + let coords = self.get_root_position(position).unwrap(); let message = ClientMessageEvent::new( 32, self.0.x_window, @@ -1273,26 +1380,25 @@ impl PlatformWindow for X11Window { 0, ], ); - self.0 - .xcb_connection - .send_event( + check_reply( + || "X11 SendEvent to show window menu failed.", + self.0.xcb.send_event( false, state.x_root_window, EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY, message, - ) - .unwrap() - .check() - .unwrap(); + ), + ) + .unwrap(); } fn start_window_move(&self) { const MOVERESIZE_MOVE: u32 = 8; - self.send_moveresize(MOVERESIZE_MOVE); + self.send_moveresize(MOVERESIZE_MOVE).unwrap(); } fn start_window_resize(&self, edge: ResizeEdge) { - self.send_moveresize(edge.to_moveresize()); + self.send_moveresize(edge.to_moveresize()).unwrap(); } fn window_decorations(&self) -> crate::Decorations { @@ -1355,9 +1461,9 @@ impl PlatformWindow for X11Window { if state.last_insets != insets { state.last_insets = insets; - self.0 - .xcb_connection - .change_property( + check_reply( + || "X11 ChangeProperty for _GTK_FRAME_EXTENTS failed.", + self.0.xcb.change_property( xproto::PropMode::REPLACE, self.0.x_window, state.atoms._GTK_FRAME_EXTENTS, @@ -1365,10 +1471,9 @@ impl PlatformWindow for X11Window { size_of::() as u8 * 8, 4, bytemuck::cast_slice::(&insets), - ) - .unwrap() - .check() - .unwrap(); + ), + ) + .unwrap(); } } @@ -1390,20 +1495,19 @@ impl PlatformWindow for X11Window { WindowDecorations::Client => [1 << 1, 0, 0, 0, 0], }; - self.0 - .xcb_connection - .change_property( + check_reply( + || "X11 ChangeProperty for _MOTIF_WM_HINTS failed.", + self.0.xcb.change_property( xproto::PropMode::REPLACE, self.0.x_window, state.atoms._MOTIF_WM_HINTS, state.atoms._MOTIF_WM_HINTS, - std::mem::size_of::() as u8 * 8, + size_of::() as u8 * 8, 5, bytemuck::cast_slice::(&hints_data), - ) - .unwrap() - .check() - .unwrap(); + ), + ) + .unwrap(); match decorations { WindowDecorations::Server => { From 1cfcdfa7ac06665c73f2a15bcdbfb65629563a15 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 22 Nov 2024 19:02:32 -0500 Subject: [PATCH 138/157] Overhaul extension registration (#21083) This PR overhauls extension registration in order to make it more modular. The `extension` crate now contains an `ExtensionHostProxy` that can be used to register various proxies that the extension host can use to interact with the rest of the system. There are now a number of different proxy traits representing the various pieces of functionality that can be provided by an extension. The respective crates that provide this functionality can implement their corresponding proxy trait in order to register a proxy that the extension host will use to register the bits of functionality provided by the extension. Release Notes: - N/A --- Cargo.lock | 49 ++- Cargo.toml | 4 + crates/assistant/src/assistant.rs | 3 +- .../src/assistant_slash_command.rs | 1 + .../src/extension_slash_command.rs | 28 +- crates/collab/Cargo.toml | 1 + .../remote_editing_collaboration_tests.rs | 4 + crates/context_servers/Cargo.toml | 1 + crates/context_servers/src/context_servers.rs | 2 + .../src/extension_context_server.rs | 78 +++++ crates/extension/Cargo.toml | 1 + crates/extension/src/extension.rs | 9 +- crates/extension/src/extension_host_proxy.rs | 324 ++++++++++++++++++ crates/extension_host/Cargo.toml | 2 + crates/extension_host/src/extension_host.rs | 146 ++------ .../src/extension_store_test.rs | 121 +------ crates/extension_host/src/headless_host.rs | 121 ++----- crates/extension_host/src/wasm_host.rs | 12 +- .../src/wasm_host/wit/since_v0_0_1.rs | 7 +- .../src/wasm_host/wit/since_v0_1_0.rs | 7 +- .../src/wasm_host/wit/since_v0_2_0.rs | 9 +- crates/extensions_ui/Cargo.toml | 7 - .../src/extension_registration_hooks.rs | 209 ----------- crates/extensions_ui/src/extensions_ui.rs | 3 - .../src/extension_indexed_docs_provider.rs | 28 +- crates/indexed_docs/src/indexed_docs.rs | 7 + crates/indexed_docs/src/registry.rs | 2 +- crates/language_extension/Cargo.toml | 25 ++ crates/language_extension/LICENSE-GPL | 1 + .../src/extension_lsp_adapter.rs | 58 +++- .../src/language_extension.rs | 51 +++ crates/remote_server/Cargo.toml | 2 + crates/remote_server/src/headless_project.rs | 6 +- .../remote_server/src/remote_editing_tests.rs | 3 + crates/remote_server/src/unix.rs | 5 + crates/snippet_provider/Cargo.toml | 1 + .../snippet_provider/src/extension_snippet.rs | 26 ++ crates/snippet_provider/src/lib.rs | 2 + crates/theme_extension/Cargo.toml | 19 + crates/theme_extension/LICENSE-GPL | 1 + crates/theme_extension/src/theme_extension.rs | 47 +++ crates/zed/Cargo.toml | 6 +- crates/zed/src/main.rs | 26 +- 43 files changed, 874 insertions(+), 591 deletions(-) create mode 100644 crates/context_servers/src/extension_context_server.rs create mode 100644 crates/extension/src/extension_host_proxy.rs delete mode 100644 crates/extensions_ui/src/extension_registration_hooks.rs create mode 100644 crates/language_extension/Cargo.toml create mode 120000 crates/language_extension/LICENSE-GPL rename crates/{extension_host => language_extension}/src/extension_lsp_adapter.rs (93%) create mode 100644 crates/language_extension/src/language_extension.rs create mode 100644 crates/snippet_provider/src/extension_snippet.rs create mode 100644 crates/theme_extension/Cargo.toml create mode 120000 crates/theme_extension/LICENSE-GPL create mode 100644 crates/theme_extension/src/theme_extension.rs diff --git a/Cargo.lock b/Cargo.lock index 514338c590..9881c23e84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2601,6 +2601,7 @@ dependencies = [ "editor", "env_logger 0.11.5", "envy", + "extension", "file_finder", "fs", "futures 0.3.31", @@ -2842,6 +2843,7 @@ dependencies = [ "anyhow", "collections", "command_palette_hooks", + "extension", "futures 0.3.31", "gpui", "log", @@ -4127,6 +4129,7 @@ dependencies = [ "language", "log", "lsp", + "parking_lot", "semantic_version", "serde", "serde_json", @@ -4178,6 +4181,7 @@ dependencies = [ "gpui", "http_client", "language", + "language_extension", "log", "lsp", "node_runtime", @@ -4196,6 +4200,7 @@ dependencies = [ "task", "tempfile", "theme", + "theme_extension", "toml 0.8.19", "url", "util", @@ -4209,21 +4214,15 @@ name = "extensions_ui" version = "0.1.0" dependencies = [ "anyhow", - "assistant_slash_command", "client", "collections", - "context_servers", "db", "editor", - "extension", "extension_host", "fs", "fuzzy", "gpui", - "indexed_docs", "language", - "log", - "lsp", "num-format", "picker", "project", @@ -4232,7 +4231,6 @@ dependencies = [ "serde", "settings", "smallvec", - "snippet_provider", "theme", "ui", "util", @@ -6533,6 +6531,23 @@ dependencies = [ "util", ] +[[package]] +name = "language_extension" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "collections", + "extension", + "futures 0.3.31", + "gpui", + "language", + "lsp", + "serde", + "serde_json", + "util", +] + [[package]] name = "language_model" version = "0.1.0" @@ -9853,6 +9868,7 @@ dependencies = [ "client", "clock", "env_logger 0.11.5", + "extension", "extension_host", "fork", "fs", @@ -9862,6 +9878,7 @@ dependencies = [ "gpui", "http_client", "language", + "language_extension", "languages", "libc", "log", @@ -11304,6 +11321,7 @@ version = "0.1.0" dependencies = [ "anyhow", "collections", + "extension", "fs", "futures 0.3.31", "gpui", @@ -12357,6 +12375,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "theme_extension" +version = "0.1.0" +dependencies = [ + "anyhow", + "extension", + "fs", + "gpui", + "theme", +] + [[package]] name = "theme_importer" version = "0.1.0" @@ -15466,7 +15495,6 @@ dependencies = [ "ashpd", "assets", "assistant", - "assistant_slash_command", "async-watch", "audio", "auto_update", @@ -15483,12 +15511,12 @@ dependencies = [ "collections", "command_palette", "command_palette_hooks", - "context_servers", "copilot", "db", "diagnostics", "editor", "env_logger 0.11.5", + "extension", "extension_host", "extensions_ui", "feature_flags", @@ -15503,11 +15531,11 @@ dependencies = [ "gpui", "http_client", "image_viewer", - "indexed_docs", "inline_completion_button", "install_cli", "journal", "language", + "language_extension", "language_model", "language_models", "language_selector", @@ -15556,6 +15584,7 @@ dependencies = [ "telemetry_events", "terminal_view", "theme", + "theme_extension", "theme_selector", "time", "toolchain_selector", diff --git a/Cargo.toml b/Cargo.toml index c12079a26a..b071ca19d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ members = [ "crates/install_cli", "crates/journal", "crates/language", + "crates/language_extension", "crates/language_model", "crates/language_models", "crates/language_selector", @@ -116,6 +117,7 @@ members = [ "crates/terminal_view", "crates/text", "crates/theme", + "crates/theme_extension", "crates/theme_importer", "crates/theme_selector", "crates/time_format", @@ -230,6 +232,7 @@ inline_completion_button = { path = "crates/inline_completion_button" } install_cli = { path = "crates/install_cli" } journal = { path = "crates/journal" } language = { path = "crates/language" } +language_extension = { path = "crates/language_extension" } language_model = { path = "crates/language_model" } language_models = { path = "crates/language_models" } language_selector = { path = "crates/language_selector" } @@ -292,6 +295,7 @@ terminal = { path = "crates/terminal" } terminal_view = { path = "crates/terminal_view" } text = { path = "crates/text" } theme = { path = "crates/theme" } +theme_extension = { path = "crates/theme_extension" } theme_importer = { path = "crates/theme_importer" } theme_selector = { path = "crates/theme_selector" } time_format = { path = "crates/time_format" } diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index 88500247c3..b891c3da2a 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -33,7 +33,6 @@ use feature_flags::FeatureFlagAppExt; use fs::Fs; use gpui::impl_actions; use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal}; -use indexed_docs::IndexedDocsRegistry; pub(crate) use inline_assistant::*; use language_model::{ LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage, @@ -275,7 +274,7 @@ pub fn init( client.telemetry().clone(), cx, ); - IndexedDocsRegistry::init_global(cx); + indexed_docs::init(cx); CommandPaletteFilter::update_global(cx, |filter, _cx| { filter.hide_namespace(Assistant::NAMESPACE); diff --git a/crates/assistant_slash_command/src/assistant_slash_command.rs b/crates/assistant_slash_command/src/assistant_slash_command.rs index 3fb2dc66b2..59d98ee770 100644 --- a/crates/assistant_slash_command/src/assistant_slash_command.rs +++ b/crates/assistant_slash_command/src/assistant_slash_command.rs @@ -18,6 +18,7 @@ use workspace::{ui::IconName, Workspace}; pub fn init(cx: &mut AppContext) { SlashCommandRegistry::default_global(cx); + extension_slash_command::init(cx); } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/crates/assistant_slash_command/src/extension_slash_command.rs b/crates/assistant_slash_command/src/extension_slash_command.rs index bfb2688066..2279f93b1c 100644 --- a/crates/assistant_slash_command/src/extension_slash_command.rs +++ b/crates/assistant_slash_command/src/extension_slash_command.rs @@ -3,17 +3,39 @@ use std::sync::{atomic::AtomicBool, Arc}; use anyhow::Result; use async_trait::async_trait; -use extension::{Extension, WorktreeDelegate}; -use gpui::{Task, WeakView, WindowContext}; +use extension::{Extension, ExtensionHostProxy, ExtensionSlashCommandProxy, WorktreeDelegate}; +use gpui::{AppContext, Task, WeakView, WindowContext}; use language::{BufferSnapshot, LspAdapterDelegate}; use ui::prelude::*; use workspace::Workspace; use crate::{ ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, - SlashCommandResult, + SlashCommandRegistry, SlashCommandResult, }; +pub fn init(cx: &mut AppContext) { + let proxy = ExtensionHostProxy::default_global(cx); + proxy.register_slash_command_proxy(SlashCommandRegistryProxy { + slash_command_registry: SlashCommandRegistry::global(cx), + }); +} + +struct SlashCommandRegistryProxy { + slash_command_registry: Arc, +} + +impl ExtensionSlashCommandProxy for SlashCommandRegistryProxy { + fn register_slash_command( + &self, + extension: Arc, + command: extension::SlashCommand, + ) { + self.slash_command_registry + .register_command(ExtensionSlashCommand::new(extension, command), false) + } +} + /// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`]. struct WorktreeDelegateAdapter(Arc); diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index a69eb53740..d3da1c2816 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -90,6 +90,7 @@ collections = { workspace = true, features = ["test-support"] } ctor.workspace = true editor = { workspace = true, features = ["test-support"] } env_logger.workspace = true +extension.workspace = true file_finder.workspace = true fs = { workspace = true, features = ["test-support"] } git = { workspace = true, features = ["test-support"] } diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index 00f52e9972..5b8d57a12a 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -1,6 +1,7 @@ use crate::tests::TestServer; use call::ActiveCall; use collections::HashSet; +use extension::ExtensionHostProxy; use fs::{FakeFs, Fs as _}; use futures::StreamExt as _; use gpui::{BackgroundExecutor, Context as _, SemanticVersion, TestAppContext, UpdateGlobal as _}; @@ -81,6 +82,7 @@ async fn test_sharing_an_ssh_remote_project( http_client: remote_http_client, node_runtime: node, languages, + extension_host_proxy: Arc::new(ExtensionHostProxy::new()), }, cx, ) @@ -243,6 +245,7 @@ async fn test_ssh_collaboration_git_branches( http_client: remote_http_client, node_runtime: node, languages, + extension_host_proxy: Arc::new(ExtensionHostProxy::new()), }, cx, ) @@ -400,6 +403,7 @@ async fn test_ssh_collaboration_formatting_with_prettier( http_client: remote_http_client, node_runtime: NodeRuntime::unavailable(), languages, + extension_host_proxy: Arc::new(ExtensionHostProxy::new()), }, cx, ) diff --git a/crates/context_servers/Cargo.toml b/crates/context_servers/Cargo.toml index de1e991887..cbd762c8c4 100644 --- a/crates/context_servers/Cargo.toml +++ b/crates/context_servers/Cargo.toml @@ -15,6 +15,7 @@ path = "src/context_servers.rs" anyhow.workspace = true collections.workspace = true command_palette_hooks.workspace = true +extension.workspace = true futures.workspace = true gpui.workspace = true log.workspace = true diff --git a/crates/context_servers/src/context_servers.rs b/crates/context_servers/src/context_servers.rs index 87a98ca14f..e6b52aaee2 100644 --- a/crates/context_servers/src/context_servers.rs +++ b/crates/context_servers/src/context_servers.rs @@ -1,4 +1,5 @@ pub mod client; +mod extension_context_server; pub mod manager; pub mod protocol; mod registry; @@ -19,6 +20,7 @@ pub const CONTEXT_SERVERS_NAMESPACE: &'static str = "context_servers"; pub fn init(cx: &mut AppContext) { ContextServerSettings::register(cx); ContextServerFactoryRegistry::default_global(cx); + extension_context_server::init(cx); CommandPaletteFilter::update_global(cx, |filter, _cx| { filter.hide_namespace(CONTEXT_SERVERS_NAMESPACE); diff --git a/crates/context_servers/src/extension_context_server.rs b/crates/context_servers/src/extension_context_server.rs new file mode 100644 index 0000000000..092816b5e6 --- /dev/null +++ b/crates/context_servers/src/extension_context_server.rs @@ -0,0 +1,78 @@ +use std::sync::Arc; + +use extension::{Extension, ExtensionContextServerProxy, ExtensionHostProxy, ProjectDelegate}; +use gpui::{AppContext, Model}; + +use crate::manager::ServerCommand; +use crate::ContextServerFactoryRegistry; + +struct ExtensionProject { + worktree_ids: Vec, +} + +impl ProjectDelegate for ExtensionProject { + fn worktree_ids(&self) -> Vec { + self.worktree_ids.clone() + } +} + +pub fn init(cx: &mut AppContext) { + let proxy = ExtensionHostProxy::default_global(cx); + proxy.register_context_server_proxy(ContextServerFactoryRegistryProxy { + context_server_factory_registry: ContextServerFactoryRegistry::global(cx), + }); +} + +struct ContextServerFactoryRegistryProxy { + context_server_factory_registry: Model, +} + +impl ExtensionContextServerProxy for ContextServerFactoryRegistryProxy { + fn register_context_server( + &self, + extension: Arc, + id: Arc, + cx: &mut AppContext, + ) { + self.context_server_factory_registry + .update(cx, |registry, _| { + registry.register_server_factory( + id.clone(), + Arc::new({ + move |project, cx| { + log::info!( + "loading command for context server {id} from extension {}", + extension.manifest().id + ); + + let id = id.clone(); + let extension = extension.clone(); + cx.spawn(|mut cx| async move { + let extension_project = + project.update(&mut cx, |project, cx| { + Arc::new(ExtensionProject { + worktree_ids: project + .visible_worktrees(cx) + .map(|worktree| worktree.read(cx).id().to_proto()) + .collect(), + }) + })?; + + let command = extension + .context_server_command(id.clone(), extension_project) + .await?; + + log::info!("loaded command for context server {id}: {command:?}"); + + Ok(ServerCommand { + path: command.command, + args: command.args, + env: Some(command.env.into_iter().collect()), + }) + }) + } + }), + ) + }); + } +} diff --git a/crates/extension/Cargo.toml b/crates/extension/Cargo.toml index a96cf7155a..b92771d09d 100644 --- a/crates/extension/Cargo.toml +++ b/crates/extension/Cargo.toml @@ -24,6 +24,7 @@ http_client.workspace = true language.workspace = true log.workspace = true lsp.workspace = true +parking_lot.workspace = true semantic_version.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/extension/src/extension.rs b/crates/extension/src/extension.rs index fe9b49909b..2eb067ca40 100644 --- a/crates/extension/src/extension.rs +++ b/crates/extension/src/extension.rs @@ -1,4 +1,5 @@ pub mod extension_builder; +mod extension_host_proxy; mod extension_manifest; mod types; @@ -9,13 +10,19 @@ use ::lsp::LanguageServerName; use anyhow::{anyhow, bail, Context as _, Result}; use async_trait::async_trait; use fs::normalize_path; -use gpui::Task; +use gpui::{AppContext, Task}; use language::LanguageName; use semantic_version::SemanticVersion; +pub use crate::extension_host_proxy::*; pub use crate::extension_manifest::*; pub use crate::types::*; +/// Initializes the `extension` crate. +pub fn init(cx: &mut AppContext) { + ExtensionHostProxy::default_global(cx); +} + #[async_trait] pub trait WorktreeDelegate: Send + Sync + 'static { fn id(&self) -> u64; diff --git a/crates/extension/src/extension_host_proxy.rs b/crates/extension/src/extension_host_proxy.rs new file mode 100644 index 0000000000..8909a6082d --- /dev/null +++ b/crates/extension/src/extension_host_proxy.rs @@ -0,0 +1,324 @@ +use std::path::PathBuf; +use std::sync::Arc; + +use anyhow::Result; +use fs::Fs; +use gpui::{AppContext, Global, ReadGlobal, SharedString, Task}; +use language::{LanguageMatcher, LanguageName, LanguageServerBinaryStatus, LoadedLanguage}; +use lsp::LanguageServerName; +use parking_lot::RwLock; + +use crate::{Extension, SlashCommand}; + +#[derive(Default)] +struct GlobalExtensionHostProxy(Arc); + +impl Global for GlobalExtensionHostProxy {} + +/// A proxy for interacting with the extension host. +/// +/// This object implements each of the individual proxy types so that their +/// methods can be called directly on it. +#[derive(Default)] +pub struct ExtensionHostProxy { + theme_proxy: RwLock>>, + grammar_proxy: RwLock>>, + language_proxy: RwLock>>, + language_server_proxy: RwLock>>, + snippet_proxy: RwLock>>, + slash_command_proxy: RwLock>>, + context_server_proxy: RwLock>>, + indexed_docs_provider_proxy: RwLock>>, +} + +impl ExtensionHostProxy { + /// Returns the global [`ExtensionHostProxy`]. + pub fn global(cx: &AppContext) -> Arc { + GlobalExtensionHostProxy::global(cx).0.clone() + } + + /// Returns the global [`ExtensionHostProxy`]. + /// + /// Inserts a default [`ExtensionHostProxy`] if one does not yet exist. + pub fn default_global(cx: &mut AppContext) -> Arc { + cx.default_global::().0.clone() + } + + pub fn new() -> Self { + Self { + theme_proxy: RwLock::default(), + grammar_proxy: RwLock::default(), + language_proxy: RwLock::default(), + language_server_proxy: RwLock::default(), + snippet_proxy: RwLock::default(), + slash_command_proxy: RwLock::default(), + context_server_proxy: RwLock::default(), + indexed_docs_provider_proxy: RwLock::default(), + } + } + + pub fn register_theme_proxy(&self, proxy: impl ExtensionThemeProxy) { + self.theme_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_grammar_proxy(&self, proxy: impl ExtensionGrammarProxy) { + self.grammar_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_language_proxy(&self, proxy: impl ExtensionLanguageProxy) { + self.language_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_language_server_proxy(&self, proxy: impl ExtensionLanguageServerProxy) { + self.language_server_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_snippet_proxy(&self, proxy: impl ExtensionSnippetProxy) { + self.snippet_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_slash_command_proxy(&self, proxy: impl ExtensionSlashCommandProxy) { + self.slash_command_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_context_server_proxy(&self, proxy: impl ExtensionContextServerProxy) { + self.context_server_proxy.write().replace(Arc::new(proxy)); + } + + pub fn register_indexed_docs_provider_proxy( + &self, + proxy: impl ExtensionIndexedDocsProviderProxy, + ) { + self.indexed_docs_provider_proxy + .write() + .replace(Arc::new(proxy)); + } +} + +pub trait ExtensionThemeProxy: Send + Sync + 'static { + fn list_theme_names(&self, theme_path: PathBuf, fs: Arc) -> Task>>; + + fn remove_user_themes(&self, themes: Vec); + + fn load_user_theme(&self, theme_path: PathBuf, fs: Arc) -> Task>; + + fn reload_current_theme(&self, cx: &mut AppContext); +} + +impl ExtensionThemeProxy for ExtensionHostProxy { + fn list_theme_names(&self, theme_path: PathBuf, fs: Arc) -> Task>> { + let Some(proxy) = self.theme_proxy.read().clone() else { + return Task::ready(Ok(Vec::new())); + }; + + proxy.list_theme_names(theme_path, fs) + } + + fn remove_user_themes(&self, themes: Vec) { + let Some(proxy) = self.theme_proxy.read().clone() else { + return; + }; + + proxy.remove_user_themes(themes) + } + + fn load_user_theme(&self, theme_path: PathBuf, fs: Arc) -> Task> { + let Some(proxy) = self.theme_proxy.read().clone() else { + return Task::ready(Ok(())); + }; + + proxy.load_user_theme(theme_path, fs) + } + + fn reload_current_theme(&self, cx: &mut AppContext) { + let Some(proxy) = self.theme_proxy.read().clone() else { + return; + }; + + proxy.reload_current_theme(cx) + } +} + +pub trait ExtensionGrammarProxy: Send + Sync + 'static { + fn register_grammars(&self, grammars: Vec<(Arc, PathBuf)>); +} + +impl ExtensionGrammarProxy for ExtensionHostProxy { + fn register_grammars(&self, grammars: Vec<(Arc, PathBuf)>) { + let Some(proxy) = self.grammar_proxy.read().clone() else { + return; + }; + + proxy.register_grammars(grammars) + } +} + +pub trait ExtensionLanguageProxy: Send + Sync + 'static { + fn register_language( + &self, + language: LanguageName, + grammar: Option>, + matcher: LanguageMatcher, + load: Arc Result + Send + Sync + 'static>, + ); + + fn remove_languages( + &self, + languages_to_remove: &[LanguageName], + grammars_to_remove: &[Arc], + ); +} + +impl ExtensionLanguageProxy for ExtensionHostProxy { + fn register_language( + &self, + language: LanguageName, + grammar: Option>, + matcher: LanguageMatcher, + load: Arc Result + Send + Sync + 'static>, + ) { + let Some(proxy) = self.language_proxy.read().clone() else { + return; + }; + + proxy.register_language(language, grammar, matcher, load) + } + + fn remove_languages( + &self, + languages_to_remove: &[LanguageName], + grammars_to_remove: &[Arc], + ) { + let Some(proxy) = self.language_proxy.read().clone() else { + return; + }; + + proxy.remove_languages(languages_to_remove, grammars_to_remove) + } +} + +pub trait ExtensionLanguageServerProxy: Send + Sync + 'static { + fn register_language_server( + &self, + extension: Arc, + language_server_id: LanguageServerName, + language: LanguageName, + ); + + fn remove_language_server( + &self, + language: &LanguageName, + language_server_id: &LanguageServerName, + ); + + fn update_language_server_status( + &self, + language_server_id: LanguageServerName, + status: LanguageServerBinaryStatus, + ); +} + +impl ExtensionLanguageServerProxy for ExtensionHostProxy { + fn register_language_server( + &self, + extension: Arc, + language_server_id: LanguageServerName, + language: LanguageName, + ) { + let Some(proxy) = self.language_server_proxy.read().clone() else { + return; + }; + + proxy.register_language_server(extension, language_server_id, language) + } + + fn remove_language_server( + &self, + language: &LanguageName, + language_server_id: &LanguageServerName, + ) { + let Some(proxy) = self.language_server_proxy.read().clone() else { + return; + }; + + proxy.remove_language_server(language, language_server_id) + } + + fn update_language_server_status( + &self, + language_server_id: LanguageServerName, + status: LanguageServerBinaryStatus, + ) { + let Some(proxy) = self.language_server_proxy.read().clone() else { + return; + }; + + proxy.update_language_server_status(language_server_id, status) + } +} + +pub trait ExtensionSnippetProxy: Send + Sync + 'static { + fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()>; +} + +impl ExtensionSnippetProxy for ExtensionHostProxy { + fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> { + let Some(proxy) = self.snippet_proxy.read().clone() else { + return Ok(()); + }; + + proxy.register_snippet(path, snippet_contents) + } +} + +pub trait ExtensionSlashCommandProxy: Send + Sync + 'static { + fn register_slash_command(&self, extension: Arc, command: SlashCommand); +} + +impl ExtensionSlashCommandProxy for ExtensionHostProxy { + fn register_slash_command(&self, extension: Arc, command: SlashCommand) { + let Some(proxy) = self.slash_command_proxy.read().clone() else { + return; + }; + + proxy.register_slash_command(extension, command) + } +} + +pub trait ExtensionContextServerProxy: Send + Sync + 'static { + fn register_context_server( + &self, + extension: Arc, + server_id: Arc, + cx: &mut AppContext, + ); +} + +impl ExtensionContextServerProxy for ExtensionHostProxy { + fn register_context_server( + &self, + extension: Arc, + server_id: Arc, + cx: &mut AppContext, + ) { + let Some(proxy) = self.context_server_proxy.read().clone() else { + return; + }; + + proxy.register_context_server(extension, server_id, cx) + } +} + +pub trait ExtensionIndexedDocsProviderProxy: Send + Sync + 'static { + fn register_indexed_docs_provider(&self, extension: Arc, provider_id: Arc); +} + +impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy { + fn register_indexed_docs_provider(&self, extension: Arc, provider_id: Arc) { + let Some(proxy) = self.indexed_docs_provider_proxy.read().clone() else { + return; + }; + + proxy.register_indexed_docs_provider(extension, provider_id) + } +} diff --git a/crates/extension_host/Cargo.toml b/crates/extension_host/Cargo.toml index 31d3df88aa..6e78654b7e 100644 --- a/crates/extension_host/Cargo.toml +++ b/crates/extension_host/Cargo.toml @@ -57,7 +57,9 @@ env_logger.workspace = true fs = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } language = { workspace = true, features = ["test-support"] } +language_extension.workspace = true parking_lot.workspace = true project = { workspace = true, features = ["test-support"] } reqwest_client.workspace = true theme = { workspace = true, features = ["test-support"] } +theme_extension.workspace = true diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 85da812795..aab5c258f5 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -1,4 +1,3 @@ -pub mod extension_lsp_adapter; pub mod extension_settings; pub mod headless_host; pub mod wasm_host; @@ -12,8 +11,12 @@ use async_tar::Archive; use client::{proto, telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse}; use collections::{btree_map, BTreeMap, HashMap, HashSet}; use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder}; -use extension::Extension; pub use extension::ExtensionManifest; +use extension::{ + ExtensionContextServerProxy, ExtensionGrammarProxy, ExtensionHostProxy, + ExtensionIndexedDocsProviderProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy, + ExtensionSlashCommandProxy, ExtensionSnippetProxy, ExtensionThemeProxy, +}; use fs::{Fs, RemoveOptions}; use futures::{ channel::{ @@ -24,15 +27,14 @@ use futures::{ select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _, }; use gpui::{ - actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, - SharedString, Task, WeakModel, + actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext, Task, + WeakModel, }; use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use language::{ LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage, QUERY_FILENAME_PREFIXES, }; -use lsp::LanguageServerName; use node_runtime::NodeRuntime; use project::ContextProviderWithTasks; use release_channel::ReleaseChannel; @@ -95,82 +97,8 @@ pub fn is_version_compatible( true } -pub trait ExtensionRegistrationHooks: Send + Sync + 'static { - fn remove_user_themes(&self, _themes: Vec) {} - - fn load_user_theme(&self, _theme_path: PathBuf, _fs: Arc) -> Task> { - Task::ready(Ok(())) - } - - fn list_theme_names( - &self, - _theme_path: PathBuf, - _fs: Arc, - ) -> Task>> { - Task::ready(Ok(Vec::new())) - } - - fn reload_current_theme(&self, _cx: &mut AppContext) {} - - fn register_language( - &self, - _language: LanguageName, - _grammar: Option>, - _matcher: language::LanguageMatcher, - _load: Arc Result + 'static + Send + Sync>, - ) { - } - - fn register_lsp_adapter( - &self, - _extension: Arc, - _language_server_id: LanguageServerName, - _language: LanguageName, - ) { - } - - fn remove_lsp_adapter(&self, _language: &LanguageName, _server_name: &LanguageServerName) {} - - fn register_wasm_grammars(&self, _grammars: Vec<(Arc, PathBuf)>) {} - - fn remove_languages( - &self, - _languages_to_remove: &[LanguageName], - _grammars_to_remove: &[Arc], - ) { - } - - fn register_slash_command( - &self, - _extension: Arc, - _command: extension::SlashCommand, - ) { - } - - fn register_context_server( - &self, - _extension: Arc, - _id: Arc, - _cx: &mut AppContext, - ) { - } - - fn register_docs_provider(&self, _extension: Arc, _provider_id: Arc) {} - - fn register_snippets(&self, _path: &PathBuf, _snippet_contents: &str) -> Result<()> { - Ok(()) - } - - fn update_lsp_status( - &self, - _server_name: lsp::LanguageServerName, - _status: language::LanguageServerBinaryStatus, - ) { - } -} - pub struct ExtensionStore { - pub registration_hooks: Arc, + pub proxy: Arc, pub builder: Arc, pub extension_index: ExtensionIndex, pub fs: Arc, @@ -240,7 +168,7 @@ pub struct ExtensionIndexLanguageEntry { actions!(zed, [ReloadExtensions]); pub fn init( - registration_hooks: Arc, + extension_host_proxy: Arc, fs: Arc, client: Arc, node_runtime: NodeRuntime, @@ -252,7 +180,7 @@ pub fn init( ExtensionStore::new( paths::extensions_dir().clone(), None, - registration_hooks, + extension_host_proxy, fs, client.http_client().clone(), client.http_client().clone(), @@ -284,7 +212,7 @@ impl ExtensionStore { pub fn new( extensions_dir: PathBuf, build_dir: Option, - extension_api: Arc, + extension_host_proxy: Arc, fs: Arc, http_client: Arc, builder_client: Arc, @@ -300,7 +228,7 @@ impl ExtensionStore { let (reload_tx, mut reload_rx) = unbounded(); let (connection_registered_tx, mut connection_registered_rx) = unbounded(); let mut this = Self { - registration_hooks: extension_api.clone(), + proxy: extension_host_proxy.clone(), extension_index: Default::default(), installed_dir, index_path, @@ -312,7 +240,7 @@ impl ExtensionStore { fs.clone(), http_client.clone(), node_runtime, - extension_api, + extension_host_proxy, work_dir, cx, ), @@ -1113,16 +1041,16 @@ impl ExtensionStore { grammars_to_remove.extend(extension.manifest.grammars.keys().cloned()); for (language_server_name, config) in extension.manifest.language_servers.iter() { for language in config.languages() { - self.registration_hooks - .remove_lsp_adapter(&language, language_server_name); + self.proxy + .remove_language_server(&language, language_server_name); } } } self.wasm_extensions .retain(|(extension, _)| !extensions_to_unload.contains(&extension.id)); - self.registration_hooks.remove_user_themes(themes_to_remove); - self.registration_hooks + self.proxy.remove_user_themes(themes_to_remove); + self.proxy .remove_languages(&languages_to_remove, &grammars_to_remove); let languages_to_add = new_index @@ -1157,8 +1085,7 @@ impl ExtensionStore { })); } - self.registration_hooks - .register_wasm_grammars(grammars_to_add); + self.proxy.register_grammars(grammars_to_add); for (language_name, language) in languages_to_add { let mut language_path = self.installed_dir.clone(); @@ -1166,7 +1093,7 @@ impl ExtensionStore { Path::new(language.extension.as_ref()), language.path.as_path(), ]); - self.registration_hooks.register_language( + self.proxy.register_language( language_name.clone(), language.grammar.clone(), language.matcher.clone(), @@ -1196,7 +1123,7 @@ impl ExtensionStore { let fs = self.fs.clone(); let wasm_host = self.wasm_host.clone(); let root_dir = self.installed_dir.clone(); - let api = self.registration_hooks.clone(); + let proxy = self.proxy.clone(); let extension_entries = extensions_to_load .iter() .filter_map(|name| new_index.extensions.get(name).cloned()) @@ -1212,13 +1139,17 @@ impl ExtensionStore { let fs = fs.clone(); async move { for theme_path in themes_to_add.into_iter() { - api.load_user_theme(theme_path, fs.clone()).await.log_err(); + proxy + .load_user_theme(theme_path, fs.clone()) + .await + .log_err(); } for snippets_path in &snippets_to_add { if let Some(snippets_contents) = fs.load(snippets_path).await.log_err() { - api.register_snippets(snippets_path, &snippets_contents) + proxy + .register_snippet(snippets_path, &snippets_contents) .log_err(); } } @@ -1259,7 +1190,7 @@ impl ExtensionStore { for (language_server_id, language_server_config) in &manifest.language_servers { for language in language_server_config.languages() { - this.registration_hooks.register_lsp_adapter( + this.proxy.register_language_server( extension.clone(), language_server_id.clone(), language.clone(), @@ -1268,7 +1199,7 @@ impl ExtensionStore { } for (slash_command_name, slash_command) in &manifest.slash_commands { - this.registration_hooks.register_slash_command( + this.proxy.register_slash_command( extension.clone(), extension::SlashCommand { name: slash_command_name.to_string(), @@ -1283,21 +1214,18 @@ impl ExtensionStore { } for (id, _context_server_entry) in &manifest.context_servers { - this.registration_hooks.register_context_server( - extension.clone(), - id.clone(), - cx, - ); + this.proxy + .register_context_server(extension.clone(), id.clone(), cx); } for (provider_id, _provider) in &manifest.indexed_docs_providers { - this.registration_hooks - .register_docs_provider(extension.clone(), provider_id.clone()); + this.proxy + .register_indexed_docs_provider(extension.clone(), provider_id.clone()); } } this.wasm_extensions.extend(wasm_extensions); - this.registration_hooks.reload_current_theme(cx); + this.proxy.reload_current_theme(cx); }) .ok(); }) @@ -1308,7 +1236,7 @@ impl ExtensionStore { let work_dir = self.wasm_host.work_dir.clone(); let extensions_dir = self.installed_dir.clone(); let index_path = self.index_path.clone(); - let extension_api = self.registration_hooks.clone(); + let proxy = self.proxy.clone(); cx.background_executor().spawn(async move { let start_time = Instant::now(); let mut index = ExtensionIndex::default(); @@ -1334,7 +1262,7 @@ impl ExtensionStore { fs.clone(), extension_dir, &mut index, - extension_api.clone(), + proxy.clone(), ) .await .log_err(); @@ -1357,7 +1285,7 @@ impl ExtensionStore { fs: Arc, extension_dir: PathBuf, index: &mut ExtensionIndex, - extension_api: Arc, + proxy: Arc, ) -> Result<()> { let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?; let extension_id = extension_manifest.id.clone(); @@ -1409,7 +1337,7 @@ impl ExtensionStore { continue; }; - let Some(theme_families) = extension_api + let Some(theme_families) = proxy .list_theme_names(theme_path.clone(), fs.clone()) .await .log_err() diff --git a/crates/extension_host/src/extension_store_test.rs b/crates/extension_host/src/extension_store_test.rs index 5d78539617..1359b5b202 100644 --- a/crates/extension_host/src/extension_store_test.rs +++ b/crates/extension_host/src/extension_store_test.rs @@ -1,20 +1,16 @@ -use crate::extension_lsp_adapter::ExtensionLspAdapter; use crate::{ Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry, ExtensionIndexThemeEntry, ExtensionManifest, ExtensionSettings, ExtensionStore, GrammarManifestEntry, SchemaVersion, RELOAD_DEBOUNCE_DURATION, }; -use anyhow::Result; use async_compression::futures::bufread::GzipEncoder; use collections::BTreeMap; -use extension::Extension; +use extension::ExtensionHostProxy; use fs::{FakeFs, Fs, RealFs}; use futures::{io::BufReader, AsyncReadExt, StreamExt}; -use gpui::{BackgroundExecutor, Context, SemanticVersion, SharedString, Task, TestAppContext}; +use gpui::{Context, SemanticVersion, TestAppContext}; use http_client::{FakeHttpClient, Response}; -use language::{ - LanguageMatcher, LanguageName, LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage, -}; +use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus}; use lsp::LanguageServerName; use node_runtime::NodeRuntime; use parking_lot::Mutex; @@ -31,91 +27,6 @@ use std::{ use theme::ThemeRegistry; use util::test::temp_tree; -use crate::ExtensionRegistrationHooks; - -struct TestExtensionRegistrationHooks { - executor: BackgroundExecutor, - language_registry: Arc, - theme_registry: Arc, -} - -impl ExtensionRegistrationHooks for TestExtensionRegistrationHooks { - fn list_theme_names(&self, path: PathBuf, fs: Arc) -> Task>> { - self.executor.spawn(async move { - let themes = theme::read_user_theme(&path, fs).await?; - Ok(themes.themes.into_iter().map(|theme| theme.name).collect()) - }) - } - - fn load_user_theme(&self, theme_path: PathBuf, fs: Arc) -> Task> { - let theme_registry = self.theme_registry.clone(); - self.executor - .spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await }) - } - - fn remove_user_themes(&self, themes: Vec) { - self.theme_registry.remove_user_themes(&themes); - } - - fn register_language( - &self, - language: language::LanguageName, - grammar: Option>, - matcher: language::LanguageMatcher, - load: Arc Result + 'static + Send + Sync>, - ) { - self.language_registry - .register_language(language, grammar, matcher, load) - } - - fn remove_languages( - &self, - languages_to_remove: &[language::LanguageName], - grammars_to_remove: &[Arc], - ) { - self.language_registry - .remove_languages(&languages_to_remove, &grammars_to_remove); - } - - fn register_wasm_grammars(&self, grammars: Vec<(Arc, PathBuf)>) { - self.language_registry.register_wasm_grammars(grammars) - } - - fn register_lsp_adapter( - &self, - extension: Arc, - language_server_id: LanguageServerName, - language: LanguageName, - ) { - self.language_registry.register_lsp_adapter( - language.clone(), - Arc::new(ExtensionLspAdapter::new( - extension, - language_server_id, - language, - )), - ); - } - - fn update_lsp_status( - &self, - server_name: lsp::LanguageServerName, - status: LanguageServerBinaryStatus, - ) { - self.language_registry - .update_lsp_status(server_name, status); - } - - fn remove_lsp_adapter( - &self, - language_name: &language::LanguageName, - server_name: &lsp::LanguageServerName, - ) { - self.language_registry - .remove_lsp_adapter(language_name, server_name); - } -} - #[cfg(test)] #[ctor::ctor] fn init_logger() { @@ -347,20 +258,18 @@ async fn test_extension_store(cx: &mut TestAppContext) { .collect(), }; - let language_registry = Arc::new(LanguageRegistry::test(cx.executor())); + let proxy = Arc::new(ExtensionHostProxy::new()); let theme_registry = Arc::new(ThemeRegistry::new(Box::new(()))); - let registration_hooks = Arc::new(TestExtensionRegistrationHooks { - executor: cx.executor(), - language_registry: language_registry.clone(), - theme_registry: theme_registry.clone(), - }); + theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor()); + let language_registry = Arc::new(LanguageRegistry::test(cx.executor())); + language_extension::init(proxy.clone(), language_registry.clone()); let node_runtime = NodeRuntime::unavailable(); let store = cx.new_model(|cx| { ExtensionStore::new( PathBuf::from("/the-extension-dir"), None, - registration_hooks.clone(), + proxy.clone(), fs.clone(), http_client.clone(), http_client.clone(), @@ -485,7 +394,7 @@ async fn test_extension_store(cx: &mut TestAppContext) { ExtensionStore::new( PathBuf::from("/the-extension-dir"), None, - registration_hooks, + proxy, fs.clone(), http_client.clone(), http_client.clone(), @@ -568,13 +477,11 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) { let project = Project::test(fs.clone(), [project_dir.as_path()], cx).await; - let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); + let proxy = Arc::new(ExtensionHostProxy::new()); let theme_registry = Arc::new(ThemeRegistry::new(Box::new(()))); - let registration_hooks = Arc::new(TestExtensionRegistrationHooks { - executor: cx.executor(), - language_registry: language_registry.clone(), - theme_registry: theme_registry.clone(), - }); + theme_extension::init(proxy.clone(), theme_registry.clone(), cx.executor()); + let language_registry = project.read_with(cx, |project, _cx| project.languages().clone()); + language_extension::init(proxy.clone(), language_registry.clone()); let node_runtime = NodeRuntime::unavailable(); let mut status_updates = language_registry.language_server_binary_statuses(); @@ -668,7 +575,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) { ExtensionStore::new( extensions_dir.clone(), Some(cache_dir), - registration_hooks, + proxy, fs.clone(), extension_client.clone(), builder_client, diff --git a/crates/extension_host/src/headless_host.rs b/crates/extension_host/src/headless_host.rs index 6ad8b71aa3..19a574b9d4 100644 --- a/crates/extension_host/src/headless_host.rs +++ b/crates/extension_host/src/headless_host.rs @@ -3,29 +3,18 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::{anyhow, Context as _, Result}; use client::{proto, TypedEnvelope}; use collections::{HashMap, HashSet}; -use extension::{Extension, ExtensionManifest}; +use extension::{ + Extension, ExtensionHostProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy, + ExtensionManifest, +}; use fs::{Fs, RemoveOptions, RenameOptions}; use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, Task, WeakModel}; use http_client::HttpClient; -use language::{LanguageConfig, LanguageName, LanguageQueries, LanguageRegistry, LoadedLanguage}; +use language::{LanguageConfig, LanguageName, LanguageQueries, LoadedLanguage}; use lsp::LanguageServerName; use node_runtime::NodeRuntime; -use crate::{ - extension_lsp_adapter::ExtensionLspAdapter, - wasm_host::{WasmExtension, WasmHost}, - ExtensionRegistrationHooks, -}; - -pub struct HeadlessExtensionStore { - pub registration_hooks: Arc, - pub fs: Arc, - pub extension_dir: PathBuf, - pub wasm_host: Arc, - pub loaded_extensions: HashMap, Arc>, - pub loaded_languages: HashMap, Vec>, - pub loaded_language_servers: HashMap, Vec<(LanguageServerName, LanguageName)>>, -} +use crate::wasm_host::{WasmExtension, WasmHost}; #[derive(Clone, Debug)] pub struct ExtensionVersion { @@ -34,28 +23,37 @@ pub struct ExtensionVersion { pub dev: bool, } +pub struct HeadlessExtensionStore { + pub fs: Arc, + pub extension_dir: PathBuf, + pub proxy: Arc, + pub wasm_host: Arc, + pub loaded_extensions: HashMap, Arc>, + pub loaded_languages: HashMap, Vec>, + pub loaded_language_servers: HashMap, Vec<(LanguageServerName, LanguageName)>>, +} + impl HeadlessExtensionStore { pub fn new( fs: Arc, http_client: Arc, - languages: Arc, extension_dir: PathBuf, + extension_host_proxy: Arc, node_runtime: NodeRuntime, cx: &mut AppContext, ) -> Model { - let registration_hooks = Arc::new(HeadlessRegistrationHooks::new(languages.clone())); cx.new_model(|cx| Self { - registration_hooks: registration_hooks.clone(), fs: fs.clone(), wasm_host: WasmHost::new( fs.clone(), http_client.clone(), node_runtime, - registration_hooks, + extension_host_proxy.clone(), extension_dir.join("work"), cx, ), extension_dir, + proxy: extension_host_proxy, loaded_extensions: Default::default(), loaded_languages: Default::default(), loaded_language_servers: Default::default(), @@ -154,7 +152,7 @@ impl HeadlessExtensionStore { config.grammar = None; - this.registration_hooks.register_language( + this.proxy.register_language( config.name.clone(), None, config.matcher.clone(), @@ -184,7 +182,7 @@ impl HeadlessExtensionStore { .entry(manifest.id.clone()) .or_default() .push((language_server_id.clone(), language.clone())); - this.registration_hooks.register_lsp_adapter( + this.proxy.register_language_server( wasm_extension.clone(), language_server_id.clone(), language.clone(), @@ -202,19 +200,20 @@ impl HeadlessExtensionStore { cx: &mut ModelContext, ) -> Task> { self.loaded_extensions.remove(extension_id); + let languages_to_remove = self .loaded_languages .remove(extension_id) .unwrap_or_default(); - self.registration_hooks - .remove_languages(&languages_to_remove, &[]); + self.proxy.remove_languages(&languages_to_remove, &[]); + for (language_server_name, language) in self .loaded_language_servers .remove(extension_id) .unwrap_or_default() { - self.registration_hooks - .remove_lsp_adapter(&language, &language_server_name); + self.proxy + .remove_language_server(&language, &language_server_name); } let path = self.extension_dir.join(&extension_id.to_string()); @@ -318,71 +317,3 @@ impl HeadlessExtensionStore { Ok(proto::Ack {}) } } - -struct HeadlessRegistrationHooks { - language_registry: Arc, -} - -impl HeadlessRegistrationHooks { - fn new(language_registry: Arc) -> Self { - Self { language_registry } - } -} - -impl ExtensionRegistrationHooks for HeadlessRegistrationHooks { - fn register_language( - &self, - language: LanguageName, - _grammar: Option>, - matcher: language::LanguageMatcher, - load: Arc Result + 'static + Send + Sync>, - ) { - log::info!("registering language: {:?}", language); - self.language_registry - .register_language(language, None, matcher, load) - } - - fn register_lsp_adapter( - &self, - extension: Arc, - language_server_id: LanguageServerName, - language: LanguageName, - ) { - log::info!("registering lsp adapter {:?}", language); - self.language_registry.register_lsp_adapter( - language.clone(), - Arc::new(ExtensionLspAdapter::new( - extension, - language_server_id, - language, - )), - ); - } - - fn register_wasm_grammars(&self, grammars: Vec<(Arc, PathBuf)>) { - self.language_registry.register_wasm_grammars(grammars) - } - - fn remove_lsp_adapter(&self, language: &LanguageName, server_name: &LanguageServerName) { - self.language_registry - .remove_lsp_adapter(language, server_name) - } - - fn remove_languages( - &self, - languages_to_remove: &[LanguageName], - _grammars_to_remove: &[Arc], - ) { - self.language_registry - .remove_languages(languages_to_remove, &[]) - } - - fn update_lsp_status( - &self, - server_name: LanguageServerName, - status: language::LanguageServerBinaryStatus, - ) { - self.language_registry - .update_lsp_status(server_name, status) - } -} diff --git a/crates/extension_host/src/wasm_host.rs b/crates/extension_host/src/wasm_host.rs index 01c57599a8..766ca8c0bb 100644 --- a/crates/extension_host/src/wasm_host.rs +++ b/crates/extension_host/src/wasm_host.rs @@ -1,11 +1,11 @@ pub mod wit; -use crate::{ExtensionManifest, ExtensionRegistrationHooks}; +use crate::ExtensionManifest; use anyhow::{anyhow, bail, Context as _, Result}; use async_trait::async_trait; use extension::{ - CodeLabel, Command, Completion, KeyValueStoreDelegate, ProjectDelegate, SlashCommand, - SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate, + CodeLabel, Command, Completion, ExtensionHostProxy, KeyValueStoreDelegate, ProjectDelegate, + SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate, }; use fs::{normalize_path, Fs}; use futures::future::LocalBoxFuture; @@ -40,7 +40,7 @@ pub struct WasmHost { release_channel: ReleaseChannel, http_client: Arc, node_runtime: NodeRuntime, - pub registration_hooks: Arc, + pub(crate) proxy: Arc, fs: Arc, pub work_dir: PathBuf, _main_thread_message_task: Task<()>, @@ -330,7 +330,7 @@ impl WasmHost { fs: Arc, http_client: Arc, node_runtime: NodeRuntime, - registration_hooks: Arc, + proxy: Arc, work_dir: PathBuf, cx: &mut AppContext, ) -> Arc { @@ -346,7 +346,7 @@ impl WasmHost { work_dir, http_client, node_runtime, - registration_hooks, + proxy, release_channel: ReleaseChannel::global(cx), _main_thread_message_task: task, main_thread_message_tx: tx, diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs b/crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs index bd1770de38..1f0891b410 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs @@ -3,7 +3,7 @@ use crate::wasm_host::wit::since_v0_0_4; use crate::wasm_host::WasmState; use anyhow::Result; use async_trait::async_trait; -use extension::WorktreeDelegate; +use extension::{ExtensionLanguageServerProxy, WorktreeDelegate}; use language::LanguageServerBinaryStatus; use semantic_version::SemanticVersion; use std::sync::{Arc, OnceLock}; @@ -149,8 +149,9 @@ impl ExtensionImports for WasmState { }; self.host - .registration_hooks - .update_lsp_status(lsp::LanguageServerName(server_name.into()), status); + .proxy + .update_language_server_status(lsp::LanguageServerName(server_name.into()), status); + Ok(()) } diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs index 18f4bc0234..c1c07a2b09 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, bail, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; -use extension::{KeyValueStoreDelegate, WorktreeDelegate}; +use extension::{ExtensionLanguageServerProxy, KeyValueStoreDelegate, WorktreeDelegate}; use futures::{io::BufReader, FutureExt as _}; use futures::{lock::Mutex, AsyncReadExt}; use language::LanguageName; @@ -495,8 +495,9 @@ impl ExtensionImports for WasmState { }; self.host - .registration_hooks - .update_lsp_status(::lsp::LanguageServerName(server_name.into()), status); + .proxy + .update_language_server_status(::lsp::LanguageServerName(server_name.into()), status); + Ok(()) } diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs index 234eec26ec..f7e11e1032 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs @@ -8,7 +8,9 @@ use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; use context_servers::manager::ContextServerSettings; -use extension::{KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate}; +use extension::{ + ExtensionLanguageServerProxy, KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate, +}; use futures::{io::BufReader, FutureExt as _}; use futures::{lock::Mutex, AsyncReadExt}; use language::{language_settings::AllLanguageSettings, LanguageName, LanguageServerBinaryStatus}; @@ -682,8 +684,9 @@ impl ExtensionImports for WasmState { }; self.host - .registration_hooks - .update_lsp_status(::lsp::LanguageServerName(server_name.into()), status); + .proxy + .update_language_server_status(::lsp::LanguageServerName(server_name.into()), status); + Ok(()) } diff --git a/crates/extensions_ui/Cargo.toml b/crates/extensions_ui/Cargo.toml index a219fe4bd4..cc6e78d6f3 100644 --- a/crates/extensions_ui/Cargo.toml +++ b/crates/extensions_ui/Cargo.toml @@ -13,21 +13,15 @@ path = "src/extensions_ui.rs" [dependencies] anyhow.workspace = true -assistant_slash_command.workspace = true client.workspace = true collections.workspace = true -context_servers.workspace = true db.workspace = true editor.workspace = true -extension.workspace = true extension_host.workspace = true fs.workspace = true fuzzy.workspace = true gpui.workspace = true -indexed_docs.workspace = true language.workspace = true -log.workspace = true -lsp.workspace = true num-format.workspace = true picker.workspace = true project.workspace = true @@ -36,7 +30,6 @@ semantic_version.workspace = true serde.workspace = true settings.workspace = true smallvec.workspace = true -snippet_provider.workspace = true theme.workspace = true ui.workspace = true util.workspace = true diff --git a/crates/extensions_ui/src/extension_registration_hooks.rs b/crates/extensions_ui/src/extension_registration_hooks.rs deleted file mode 100644 index 1b427cd187..0000000000 --- a/crates/extensions_ui/src/extension_registration_hooks.rs +++ /dev/null @@ -1,209 +0,0 @@ -use std::{path::PathBuf, sync::Arc}; - -use anyhow::Result; -use assistant_slash_command::{ExtensionSlashCommand, SlashCommandRegistry}; -use context_servers::manager::ServerCommand; -use context_servers::ContextServerFactoryRegistry; -use extension::{Extension, ProjectDelegate}; -use extension_host::extension_lsp_adapter::ExtensionLspAdapter; -use fs::Fs; -use gpui::{AppContext, BackgroundExecutor, Model, Task}; -use indexed_docs::{ExtensionIndexedDocsProvider, IndexedDocsRegistry, ProviderId}; -use language::{LanguageName, LanguageRegistry, LanguageServerBinaryStatus, LoadedLanguage}; -use lsp::LanguageServerName; -use snippet_provider::SnippetRegistry; -use theme::{ThemeRegistry, ThemeSettings}; -use ui::SharedString; - -struct ExtensionProject { - worktree_ids: Vec, -} - -impl ProjectDelegate for ExtensionProject { - fn worktree_ids(&self) -> Vec { - self.worktree_ids.clone() - } -} - -pub struct ConcreteExtensionRegistrationHooks { - slash_command_registry: Arc, - theme_registry: Arc, - indexed_docs_registry: Arc, - snippet_registry: Arc, - language_registry: Arc, - context_server_factory_registry: Model, - executor: BackgroundExecutor, -} - -impl ConcreteExtensionRegistrationHooks { - pub fn new( - theme_registry: Arc, - slash_command_registry: Arc, - indexed_docs_registry: Arc, - snippet_registry: Arc, - language_registry: Arc, - context_server_factory_registry: Model, - cx: &AppContext, - ) -> Arc { - Arc::new(Self { - theme_registry, - slash_command_registry, - indexed_docs_registry, - snippet_registry, - language_registry, - context_server_factory_registry, - executor: cx.background_executor().clone(), - }) - } -} - -impl extension_host::ExtensionRegistrationHooks for ConcreteExtensionRegistrationHooks { - fn remove_user_themes(&self, themes: Vec) { - self.theme_registry.remove_user_themes(&themes); - } - - fn load_user_theme(&self, theme_path: PathBuf, fs: Arc) -> Task> { - let theme_registry = self.theme_registry.clone(); - self.executor - .spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await }) - } - - fn register_slash_command( - &self, - extension: Arc, - command: extension::SlashCommand, - ) { - self.slash_command_registry - .register_command(ExtensionSlashCommand::new(extension, command), false) - } - - fn register_context_server( - &self, - extension: Arc, - id: Arc, - cx: &mut AppContext, - ) { - self.context_server_factory_registry - .update(cx, |registry, _| { - registry.register_server_factory( - id.clone(), - Arc::new({ - move |project, cx| { - log::info!( - "loading command for context server {id} from extension {}", - extension.manifest().id - ); - - let id = id.clone(); - let extension = extension.clone(); - cx.spawn(|mut cx| async move { - let extension_project = - project.update(&mut cx, |project, cx| { - Arc::new(ExtensionProject { - worktree_ids: project - .visible_worktrees(cx) - .map(|worktree| worktree.read(cx).id().to_proto()) - .collect(), - }) - })?; - - let command = extension - .context_server_command(id.clone(), extension_project) - .await?; - - log::info!("loaded command for context server {id}: {command:?}"); - - Ok(ServerCommand { - path: command.command, - args: command.args, - env: Some(command.env.into_iter().collect()), - }) - }) - } - }), - ) - }); - } - - fn register_docs_provider(&self, extension: Arc, provider_id: Arc) { - self.indexed_docs_registry - .register_provider(Box::new(ExtensionIndexedDocsProvider::new( - extension, - ProviderId(provider_id), - ))); - } - - fn register_snippets(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> { - self.snippet_registry - .register_snippets(path, snippet_contents) - } - - fn update_lsp_status( - &self, - server_name: lsp::LanguageServerName, - status: LanguageServerBinaryStatus, - ) { - self.language_registry - .update_lsp_status(server_name, status); - } - - fn register_lsp_adapter( - &self, - extension: Arc, - language_server_id: LanguageServerName, - language: LanguageName, - ) { - self.language_registry.register_lsp_adapter( - language.clone(), - Arc::new(ExtensionLspAdapter::new( - extension, - language_server_id, - language, - )), - ); - } - - fn remove_lsp_adapter( - &self, - language_name: &language::LanguageName, - server_name: &lsp::LanguageServerName, - ) { - self.language_registry - .remove_lsp_adapter(language_name, server_name); - } - - fn remove_languages( - &self, - languages_to_remove: &[language::LanguageName], - grammars_to_remove: &[Arc], - ) { - self.language_registry - .remove_languages(&languages_to_remove, &grammars_to_remove); - } - - fn register_wasm_grammars(&self, grammars: Vec<(Arc, PathBuf)>) { - self.language_registry.register_wasm_grammars(grammars) - } - - fn register_language( - &self, - language: language::LanguageName, - grammar: Option>, - matcher: language::LanguageMatcher, - load: Arc Result + 'static + Send + Sync>, - ) { - self.language_registry - .register_language(language, grammar, matcher, load) - } - - fn reload_current_theme(&self, cx: &mut AppContext) { - ThemeSettings::reload_current_theme(cx) - } - - fn list_theme_names(&self, path: PathBuf, fs: Arc) -> Task>> { - self.executor.spawn(async move { - let themes = theme::read_user_theme(&path, fs).await?; - Ok(themes.themes.into_iter().map(|theme| theme.name).collect()) - }) - } -} diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 077d80b9b8..eaffdafa41 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -1,10 +1,7 @@ mod components; -mod extension_registration_hooks; mod extension_suggest; mod extension_version_selector; -pub use extension_registration_hooks::ConcreteExtensionRegistrationHooks; - use std::ops::DerefMut; use std::sync::OnceLock; use std::time::Duration; diff --git a/crates/indexed_docs/src/extension_indexed_docs_provider.rs b/crates/indexed_docs/src/extension_indexed_docs_provider.rs index ed006546fe..25b0f16357 100644 --- a/crates/indexed_docs/src/extension_indexed_docs_provider.rs +++ b/crates/indexed_docs/src/extension_indexed_docs_provider.rs @@ -3,9 +3,33 @@ use std::sync::Arc; use anyhow::Result; use async_trait::async_trait; -use extension::Extension; +use extension::{Extension, ExtensionHostProxy, ExtensionIndexedDocsProviderProxy}; +use gpui::AppContext; -use crate::{IndexedDocsDatabase, IndexedDocsProvider, PackageName, ProviderId}; +use crate::{ + IndexedDocsDatabase, IndexedDocsProvider, IndexedDocsRegistry, PackageName, ProviderId, +}; + +pub fn init(cx: &mut AppContext) { + let proxy = ExtensionHostProxy::default_global(cx); + proxy.register_indexed_docs_provider_proxy(IndexedDocsRegistryProxy { + indexed_docs_registry: IndexedDocsRegistry::global(cx), + }); +} + +struct IndexedDocsRegistryProxy { + indexed_docs_registry: Arc, +} + +impl ExtensionIndexedDocsProviderProxy for IndexedDocsRegistryProxy { + fn register_indexed_docs_provider(&self, extension: Arc, provider_id: Arc) { + self.indexed_docs_registry + .register_provider(Box::new(ExtensionIndexedDocsProvider::new( + extension, + ProviderId(provider_id), + ))); + } +} pub struct ExtensionIndexedDocsProvider { extension: Arc, diff --git a/crates/indexed_docs/src/indexed_docs.rs b/crates/indexed_docs/src/indexed_docs.rs index 95e5c62335..42672cd220 100644 --- a/crates/indexed_docs/src/indexed_docs.rs +++ b/crates/indexed_docs/src/indexed_docs.rs @@ -3,7 +3,14 @@ mod providers; mod registry; mod store; +use gpui::AppContext; + pub use crate::extension_indexed_docs_provider::ExtensionIndexedDocsProvider; pub use crate::providers::rustdoc::*; pub use crate::registry::*; pub use crate::store::*; + +pub fn init(cx: &mut AppContext) { + IndexedDocsRegistry::init_global(cx); + extension_indexed_docs_provider::init(cx); +} diff --git a/crates/indexed_docs/src/registry.rs b/crates/indexed_docs/src/registry.rs index fa3425466c..6332e6c4b0 100644 --- a/crates/indexed_docs/src/registry.rs +++ b/crates/indexed_docs/src/registry.rs @@ -20,7 +20,7 @@ impl IndexedDocsRegistry { GlobalIndexedDocsRegistry::global(cx).0.clone() } - pub fn init_global(cx: &mut AppContext) { + pub(crate) fn init_global(cx: &mut AppContext) { GlobalIndexedDocsRegistry::set_global( cx, GlobalIndexedDocsRegistry(Arc::new(Self::new(cx.background_executor().clone()))), diff --git a/crates/language_extension/Cargo.toml b/crates/language_extension/Cargo.toml new file mode 100644 index 0000000000..3d1e4d0a64 --- /dev/null +++ b/crates/language_extension/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "language_extension" +version = "0.1.0" +edition = "2021" +publish = false +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/language_extension.rs" + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +collections.workspace = true +extension.workspace = true +futures.workspace = true +gpui.workspace = true +language.workspace = true +lsp.workspace = true +serde.workspace = true +serde_json.workspace = true +util.workspace = true diff --git a/crates/language_extension/LICENSE-GPL b/crates/language_extension/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/language_extension/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/extension_host/src/extension_lsp_adapter.rs b/crates/language_extension/src/extension_lsp_adapter.rs similarity index 93% rename from crates/extension_host/src/extension_lsp_adapter.rs rename to crates/language_extension/src/extension_lsp_adapter.rs index 069eddba57..eab9529fe0 100644 --- a/crates/extension_host/src/extension_lsp_adapter.rs +++ b/crates/language_extension/src/extension_lsp_adapter.rs @@ -1,22 +1,28 @@ +use std::any::Any; +use std::ops::Range; +use std::path::PathBuf; +use std::pin::Pin; +use std::sync::Arc; + use anyhow::{Context, Result}; use async_trait::async_trait; use collections::HashMap; -use extension::{Extension, WorktreeDelegate}; +use extension::{Extension, ExtensionLanguageServerProxy, WorktreeDelegate}; use futures::{Future, FutureExt}; use gpui::AsyncAppContext; use language::{ - CodeLabel, HighlightId, Language, LanguageName, LanguageToolchainStore, LspAdapter, - LspAdapterDelegate, + CodeLabel, HighlightId, Language, LanguageName, LanguageServerBinaryStatus, + LanguageToolchainStore, LspAdapter, LspAdapterDelegate, }; use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName}; use serde::Serialize; use serde_json::Value; -use std::ops::Range; -use std::{any::Any, path::PathBuf, pin::Pin, sync::Arc}; use util::{maybe, ResultExt}; +use crate::LanguageServerRegistryProxy; + /// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`]. -pub struct WorktreeDelegateAdapter(pub Arc); +struct WorktreeDelegateAdapter(pub Arc); #[async_trait] impl WorktreeDelegate for WorktreeDelegateAdapter { @@ -44,14 +50,50 @@ impl WorktreeDelegate for WorktreeDelegateAdapter { } } -pub struct ExtensionLspAdapter { +impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy { + fn register_language_server( + &self, + extension: Arc, + language_server_id: LanguageServerName, + language: LanguageName, + ) { + self.language_registry.register_lsp_adapter( + language.clone(), + Arc::new(ExtensionLspAdapter::new( + extension, + language_server_id, + language, + )), + ); + } + + fn remove_language_server( + &self, + language: &LanguageName, + language_server_id: &LanguageServerName, + ) { + self.language_registry + .remove_lsp_adapter(language, language_server_id); + } + + fn update_language_server_status( + &self, + language_server_id: LanguageServerName, + status: LanguageServerBinaryStatus, + ) { + self.language_registry + .update_lsp_status(language_server_id, status); + } +} + +struct ExtensionLspAdapter { extension: Arc, language_server_id: LanguageServerName, language_name: LanguageName, } impl ExtensionLspAdapter { - pub fn new( + fn new( extension: Arc, language_server_id: LanguageServerName, language_name: LanguageName, diff --git a/crates/language_extension/src/language_extension.rs b/crates/language_extension/src/language_extension.rs new file mode 100644 index 0000000000..d8ffc71d7c --- /dev/null +++ b/crates/language_extension/src/language_extension.rs @@ -0,0 +1,51 @@ +mod extension_lsp_adapter; + +use std::path::PathBuf; +use std::sync::Arc; + +use anyhow::Result; +use extension::{ExtensionGrammarProxy, ExtensionHostProxy, ExtensionLanguageProxy}; +use language::{LanguageMatcher, LanguageName, LanguageRegistry, LoadedLanguage}; + +pub fn init( + extension_host_proxy: Arc, + language_registry: Arc, +) { + let language_server_registry_proxy = LanguageServerRegistryProxy { language_registry }; + extension_host_proxy.register_grammar_proxy(language_server_registry_proxy.clone()); + extension_host_proxy.register_language_proxy(language_server_registry_proxy.clone()); + extension_host_proxy.register_language_server_proxy(language_server_registry_proxy); +} + +#[derive(Clone)] +struct LanguageServerRegistryProxy { + language_registry: Arc, +} + +impl ExtensionGrammarProxy for LanguageServerRegistryProxy { + fn register_grammars(&self, grammars: Vec<(Arc, PathBuf)>) { + self.language_registry.register_wasm_grammars(grammars) + } +} + +impl ExtensionLanguageProxy for LanguageServerRegistryProxy { + fn register_language( + &self, + language: LanguageName, + grammar: Option>, + matcher: LanguageMatcher, + load: Arc Result + Send + Sync + 'static>, + ) { + self.language_registry + .register_language(language, grammar, matcher, load); + } + + fn remove_languages( + &self, + languages_to_remove: &[LanguageName], + grammars_to_remove: &[Arc], + ) { + self.language_registry + .remove_languages(&languages_to_remove, &grammars_to_remove); + } +} diff --git a/crates/remote_server/Cargo.toml b/crates/remote_server/Cargo.toml index d46fb8df56..82853217dc 100644 --- a/crates/remote_server/Cargo.toml +++ b/crates/remote_server/Cargo.toml @@ -29,6 +29,7 @@ chrono.workspace = true clap.workspace = true client.workspace = true env_logger.workspace = true +extension.workspace = true extension_host.workspace = true fs.workspace = true futures.workspace = true @@ -37,6 +38,7 @@ git_hosting_providers.workspace = true gpui.workspace = true http_client.workspace = true language.workspace = true +language_extension.workspace = true languages.workspace = true log.workspace = true lsp.workspace = true diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 28cd6e115c..2fb8330603 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -1,4 +1,5 @@ use anyhow::{anyhow, Result}; +use extension::ExtensionHostProxy; use extension_host::headless_host::HeadlessExtensionStore; use fs::Fs; use gpui::{AppContext, AsyncAppContext, Context as _, Model, ModelContext, PromptLevel}; @@ -47,6 +48,7 @@ pub struct HeadlessAppState { pub http_client: Arc, pub node_runtime: NodeRuntime, pub languages: Arc, + pub extension_host_proxy: Arc, } impl HeadlessProject { @@ -63,9 +65,11 @@ impl HeadlessProject { http_client, node_runtime, languages, + extension_host_proxy: proxy, }: HeadlessAppState, cx: &mut ModelContext, ) -> Self { + language_extension::init(proxy.clone(), languages.clone()); languages::init(languages.clone(), node_runtime.clone(), cx); let worktree_store = cx.new_model(|cx| { @@ -152,8 +156,8 @@ impl HeadlessProject { let extensions = HeadlessExtensionStore::new( fs.clone(), http_client.clone(), - languages.clone(), paths::remote_extensions_dir().to_path_buf(), + proxy, node_runtime, cx, ); diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index 3a9803287a..bdb862c5af 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -1,6 +1,7 @@ use crate::headless_project::HeadlessProject; use client::{Client, UserStore}; use clock::FakeSystemClock; +use extension::ExtensionHostProxy; use fs::{FakeFs, Fs}; use gpui::{Context, Model, SemanticVersion, TestAppContext}; use http_client::{BlockedHttpClient, FakeHttpClient}; @@ -1234,6 +1235,7 @@ pub async fn init_test( let http_client = Arc::new(BlockedHttpClient); let node_runtime = NodeRuntime::unavailable(); let languages = Arc::new(LanguageRegistry::new(cx.executor())); + let proxy = Arc::new(ExtensionHostProxy::new()); server_cx.update(HeadlessProject::init); let headless = server_cx.new_model(|cx| { client::init_settings(cx); @@ -1245,6 +1247,7 @@ pub async fn init_test( http_client, node_runtime, languages, + extension_host_proxy: proxy, }, cx, ) diff --git a/crates/remote_server/src/unix.rs b/crates/remote_server/src/unix.rs index 467fd452f8..18378ec8e9 100644 --- a/crates/remote_server/src/unix.rs +++ b/crates/remote_server/src/unix.rs @@ -3,6 +3,7 @@ use crate::HeadlessProject; use anyhow::{anyhow, Context, Result}; use chrono::Utc; use client::{telemetry, ProxySettings}; +use extension::ExtensionHostProxy; use fs::{Fs, RealFs}; use futures::channel::mpsc; use futures::{select, select_biased, AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt}; @@ -434,6 +435,9 @@ pub fn execute_run( GitHostingProviderRegistry::set_global(git_hosting_provider_registry, cx); git_hosting_providers::init(cx); + extension::init(cx); + let extension_host_proxy = ExtensionHostProxy::global(cx); + let project = cx.new_model(|cx| { let fs = Arc::new(RealFs::new(Default::default(), None)); let node_settings_rx = initialize_settings(session.clone(), fs.clone(), cx); @@ -466,6 +470,7 @@ pub fn execute_run( http_client, node_runtime, languages, + extension_host_proxy, }, cx, ) diff --git a/crates/snippet_provider/Cargo.toml b/crates/snippet_provider/Cargo.toml index 95ab19ebb6..aa4e1a5f84 100644 --- a/crates/snippet_provider/Cargo.toml +++ b/crates/snippet_provider/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] anyhow.workspace = true collections.workspace = true +extension.workspace = true fs.workspace = true futures.workspace = true gpui.workspace = true diff --git a/crates/snippet_provider/src/extension_snippet.rs b/crates/snippet_provider/src/extension_snippet.rs new file mode 100644 index 0000000000..41a7c886e1 --- /dev/null +++ b/crates/snippet_provider/src/extension_snippet.rs @@ -0,0 +1,26 @@ +use std::path::PathBuf; +use std::sync::Arc; + +use anyhow::Result; +use extension::{ExtensionHostProxy, ExtensionSnippetProxy}; +use gpui::AppContext; + +use crate::SnippetRegistry; + +pub fn init(cx: &mut AppContext) { + let proxy = ExtensionHostProxy::default_global(cx); + proxy.register_snippet_proxy(SnippetRegistryProxy { + snippet_registry: SnippetRegistry::global(cx), + }); +} + +struct SnippetRegistryProxy { + snippet_registry: Arc, +} + +impl ExtensionSnippetProxy for SnippetRegistryProxy { + fn register_snippet(&self, path: &PathBuf, snippet_contents: &str) -> Result<()> { + self.snippet_registry + .register_snippets(path, snippet_contents) + } +} diff --git a/crates/snippet_provider/src/lib.rs b/crates/snippet_provider/src/lib.rs index 17d60d25a0..34aa1ebefc 100644 --- a/crates/snippet_provider/src/lib.rs +++ b/crates/snippet_provider/src/lib.rs @@ -1,3 +1,4 @@ +mod extension_snippet; mod format; mod registry; @@ -18,6 +19,7 @@ use util::ResultExt; pub fn init(cx: &mut AppContext) { SnippetRegistry::init_global(cx); + extension_snippet::init(cx); } // Is `None` if the snippet file is global. diff --git a/crates/theme_extension/Cargo.toml b/crates/theme_extension/Cargo.toml new file mode 100644 index 0000000000..1e12f037b9 --- /dev/null +++ b/crates/theme_extension/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "theme_extension" +version = "0.1.0" +edition = "2021" +publish = false +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/theme_extension.rs" + +[dependencies] +anyhow.workspace = true +extension.workspace = true +fs.workspace = true +gpui.workspace = true +theme.workspace = true diff --git a/crates/theme_extension/LICENSE-GPL b/crates/theme_extension/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/theme_extension/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/theme_extension/src/theme_extension.rs b/crates/theme_extension/src/theme_extension.rs new file mode 100644 index 0000000000..0266db324b --- /dev/null +++ b/crates/theme_extension/src/theme_extension.rs @@ -0,0 +1,47 @@ +use std::path::PathBuf; +use std::sync::Arc; + +use anyhow::Result; +use extension::{ExtensionHostProxy, ExtensionThemeProxy}; +use fs::Fs; +use gpui::{AppContext, BackgroundExecutor, SharedString, Task}; +use theme::{ThemeRegistry, ThemeSettings}; + +pub fn init( + extension_host_proxy: Arc, + theme_registry: Arc, + executor: BackgroundExecutor, +) { + extension_host_proxy.register_theme_proxy(ThemeRegistryProxy { + theme_registry, + executor, + }); +} + +struct ThemeRegistryProxy { + theme_registry: Arc, + executor: BackgroundExecutor, +} + +impl ExtensionThemeProxy for ThemeRegistryProxy { + fn list_theme_names(&self, theme_path: PathBuf, fs: Arc) -> Task>> { + self.executor.spawn(async move { + let themes = theme::read_user_theme(&theme_path, fs).await?; + Ok(themes.themes.into_iter().map(|theme| theme.name).collect()) + }) + } + + fn remove_user_themes(&self, themes: Vec) { + self.theme_registry.remove_user_themes(&themes); + } + + fn load_user_theme(&self, theme_path: PathBuf, fs: Arc) -> Task> { + let theme_registry = self.theme_registry.clone(); + self.executor + .spawn(async move { theme_registry.load_user_theme(&theme_path, fs).await }) + } + + fn reload_current_theme(&self, cx: &mut AppContext) { + ThemeSettings::reload_current_theme(cx) + } +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 52ec265480..755076e360 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -19,7 +19,6 @@ activity_indicator.workspace = true anyhow.workspace = true assets.workspace = true assistant.workspace = true -assistant_slash_command.workspace = true async-watch.workspace = true audio.workspace = true auto_update.workspace = true @@ -36,12 +35,12 @@ collab_ui.workspace = true collections.workspace = true command_palette.workspace = true command_palette_hooks.workspace = true -context_servers.workspace = true copilot.workspace = true db.workspace = true diagnostics.workspace = true editor.workspace = true env_logger.workspace = true +extension.workspace = true extension_host.workspace = true extensions_ui.workspace = true feature_flags.workspace = true @@ -56,11 +55,11 @@ go_to_line.workspace = true gpui = { workspace = true, features = ["wayland", "x11", "font-kit"] } http_client.workspace = true image_viewer.workspace = true -indexed_docs.workspace = true inline_completion_button.workspace = true install_cli.workspace = true journal.workspace = true language.workspace = true +language_extension.workspace = true language_model.workspace = true language_models.workspace = true language_selector.workspace = true @@ -109,6 +108,7 @@ tasks_ui.workspace = true telemetry_events.workspace = true terminal_view.workspace = true theme.workspace = true +theme_extension.workspace = true theme_selector.workspace = true time.workspace = true toolchain_selector.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e96a70f91d..73b0e0f199 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -5,16 +5,15 @@ mod reliability; mod zed; use anyhow::{anyhow, Context as _, Result}; -use assistant_slash_command::SlashCommandRegistry; use chrono::Offset; use clap::{command, Parser}; use cli::FORCE_CLI_MODE_ENV_VAR_NAME; use client::{parse_zed_link, Client, ProxySettings, UserStore}; use collab_ui::channel_view::ChannelView; -use context_servers::ContextServerFactoryRegistry; use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE}; use editor::Editor; use env_logger::Builder; +use extension::ExtensionHostProxy; use fs::{Fs, RealFs}; use futures::{future, StreamExt}; use git::GitHostingProviderRegistry; @@ -23,7 +22,6 @@ use gpui::{ VisualContext, }; use http_client::{read_proxy_from_env, Uri}; -use indexed_docs::IndexedDocsRegistry; use language::LanguageRegistry; use log::LevelFilter; use reqwest_client::ReqwestClient; @@ -40,7 +38,6 @@ use settings::{ }; use simplelog::ConfigBuilder; use smol::process::Command; -use snippet_provider::SnippetRegistry; use std::{ env, fs::OpenOptions, @@ -284,6 +281,9 @@ fn main() { OpenListener::set_global(cx, open_listener.clone()); + extension::init(cx); + let extension_host_proxy = ExtensionHostProxy::global(cx); + let client = Client::production(cx); cx.set_http_client(client.http_client().clone()); let mut languages = LanguageRegistry::new(cx.background_executor().clone()); @@ -317,6 +317,7 @@ fn main() { let node_runtime = NodeRuntime::new(client.http_client(), rx); language::init(cx); + language_extension::init(extension_host_proxy.clone(), languages.clone()); languages::init(languages.clone(), node_runtime.clone(), cx); let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx)); let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx)); @@ -326,7 +327,6 @@ fn main() { zed::init(cx); project::Project::init(&client, cx); client::init(&client, cx); - language::init(cx); let telemetry = client.telemetry(); telemetry.start( system_id.as_ref().map(|id| id.to_string()), @@ -376,6 +376,11 @@ fn main() { SystemAppearance::init(cx); theme::init(theme::LoadThemes::All(Box::new(Assets)), cx); + theme_extension::init( + extension_host_proxy.clone(), + ThemeRegistry::global(cx), + cx.background_executor().clone(), + ); command_palette::init(cx); let copilot_language_server_id = app_state.languages.next_language_server_id(); copilot::init( @@ -407,17 +412,8 @@ fn main() { app_state.client.telemetry().clone(), cx, ); - let api = extensions_ui::ConcreteExtensionRegistrationHooks::new( - ThemeRegistry::global(cx), - SlashCommandRegistry::global(cx), - IndexedDocsRegistry::global(cx), - SnippetRegistry::global(cx), - app_state.languages.clone(), - ContextServerFactoryRegistry::global(cx), - cx, - ); extension_host::init( - api, + extension_host_proxy, app_state.fs.clone(), app_state.client.clone(), app_state.node_runtime.clone(), From 9833756224180f9e94704c9a07906e7850ff0351 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Sat, 23 Nov 2024 02:21:19 +0200 Subject: [PATCH 139/157] Fix file finder menu actions (#21087) Closes https://github.com/zed-industries/zed/issues/21077 * BREAKING: rename `file_finder::OpenMenu` into `file_finder::ToggleMenu` * Display the keybinding for menu toggling when the menu is open * Fix `enter` not working in the menu Release Notes: - Fixed enter not working and menu toggle binding not shown in the file finder menu --- assets/keymaps/default-linux.json | 7 +++++- assets/keymaps/default-macos.json | 7 +++++- crates/file_finder/src/file_finder.rs | 31 ++++++++++++++------------ crates/ui/src/components/keybinding.rs | 2 +- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 313ee024b5..2eedc1c839 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -649,11 +649,16 @@ "tab": "channel_modal::ToggleMode" } }, + { + "context": "FileFinder", + "bindings": { + "ctrl": "file_finder::ToggleMenu" + } + }, { "context": "FileFinder && !menu_open", "bindings": { "ctrl-shift-p": "file_finder::SelectPrev", - "ctrl": "file_finder::OpenMenu", "ctrl-j": "pane::SplitDown", "ctrl-k": "pane::SplitUp", "ctrl-h": "pane::SplitLeft", diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 025ba4d69d..963d48ba5e 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -650,11 +650,16 @@ "tab": "channel_modal::ToggleMode" } }, + { + "context": "FileFinder", + "bindings": { + "cmd": "file_finder::ToggleMenu" + } + }, { "context": "FileFinder && !menu_open", "bindings": { "cmd-shift-p": "file_finder::SelectPrev", - "cmd": "file_finder::OpenMenu", "cmd-j": "pane::SplitDown", "cmd-k": "pane::SplitUp", "cmd-h": "pane::SplitLeft", diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 138a02d1f6..6a758211f8 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -42,7 +42,7 @@ use workspace::{ Workspace, }; -actions!(file_finder, [SelectPrev, OpenMenu]); +actions!(file_finder, [SelectPrev, ToggleMenu]); impl ModalView for FileFinder { fn on_before_dismiss(&mut self, cx: &mut ViewContext) -> workspace::DismissDecision { @@ -189,10 +189,12 @@ impl FileFinder { cx.dispatch_action(Box::new(menu::SelectPrev)); } - fn handle_open_menu(&mut self, _: &OpenMenu, cx: &mut ViewContext) { + fn handle_toggle_menu(&mut self, _: &ToggleMenu, cx: &mut ViewContext) { self.picker.update(cx, |picker, cx| { let menu_handle = &picker.delegate.popover_menu_handle; - if !menu_handle.is_deployed() { + if menu_handle.is_deployed() { + menu_handle.hide(cx); + } else { menu_handle.show(cx); } }); @@ -282,7 +284,7 @@ impl Render for FileFinder { .w(modal_max_width) .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed)) .on_action(cx.listener(Self::handle_select_prev)) - .on_action(cx.listener(Self::handle_open_menu)) + .on_action(cx.listener(Self::handle_toggle_menu)) .on_action(cx.listener(Self::go_to_file_split_left)) .on_action(cx.listener(Self::go_to_file_split_right)) .on_action(cx.listener(Self::go_to_file_split_up)) @@ -1242,6 +1244,7 @@ impl PickerDelegate for FileFinderDelegate { } fn render_footer(&self, cx: &mut ViewContext>) -> Option { + let context = self.focus_handle.clone(); Some( h_flex() .w_full() @@ -1263,19 +1266,19 @@ impl PickerDelegate for FileFinderDelegate { .trigger( Button::new("actions-trigger", "Split Options") .selected_label_color(Color::Accent) - .key_binding(KeyBinding::for_action_in( - &OpenMenu, - &self.focus_handle, - cx, - )), + .key_binding(KeyBinding::for_action_in(&ToggleMenu, &context, cx)), ) .menu({ move |cx| { - Some(ContextMenu::build(cx, move |menu, _| { - menu.action("Split Left", pane::SplitLeft.boxed_clone()) - .action("Split Right", pane::SplitRight.boxed_clone()) - .action("Split Up", pane::SplitUp.boxed_clone()) - .action("Split Down", pane::SplitDown.boxed_clone()) + Some(ContextMenu::build(cx, { + let context = context.clone(); + move |menu, _| { + menu.context(context) + .action("Split Left", pane::SplitLeft.boxed_clone()) + .action("Split Right", pane::SplitRight.boxed_clone()) + .action("Split Up", pane::SplitUp.boxed_clone()) + .action("Split Down", pane::SplitDown.boxed_clone()) + } })) } }), diff --git a/crates/ui/src/components/keybinding.rs b/crates/ui/src/components/keybinding.rs index 770e46eafd..328481de6e 100644 --- a/crates/ui/src/components/keybinding.rs +++ b/crates/ui/src/components/keybinding.rs @@ -3,7 +3,7 @@ use crate::PlatformStyle; use crate::{h_flex, prelude::*, Icon, IconName, IconSize}; use gpui::{relative, Action, FocusHandle, IntoElement, Keystroke, WindowContext}; -#[derive(IntoElement, Clone)] +#[derive(Debug, IntoElement, Clone)] pub struct KeyBinding { /// A keybinding consists of a key and a set of modifier keys. /// More then one keybinding produces a chord. From 5766afe7102b8f9af3fe1b025d9db5c6f7c86ca7 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Fri, 22 Nov 2024 16:31:11 -0800 Subject: [PATCH 140/157] Pass through remote kernel's language on legacy selection (#21088) When selecting an active kernel based on legacy usage, have remote kernels defer to language within kernelspec. Release Notes: - N/A --- crates/repl/src/repl_store.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/repl/src/repl_store.rs b/crates/repl/src/repl_store.rs index 27854c0eee..49c24bce68 100644 --- a/crates/repl/src/repl_store.rs +++ b/crates/repl/src/repl_store.rs @@ -258,8 +258,9 @@ impl ReplStore { runtime_specification.kernelspec.language.to_lowercase() == language_at_cursor.code_fence_block_name().to_lowercase() } - KernelSpecification::Remote(_) => { - unimplemented!() + KernelSpecification::Remote(remote_spec) => { + remote_spec.kernelspec.language.to_lowercase() + == language_at_cursor.code_fence_block_name().to_lowercase() } }) .cloned() From 984bb192baa0d1f5cc27269c4929d8899cb07879 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 22 Nov 2024 20:40:39 -0700 Subject: [PATCH 141/157] Send llm events to snowflake too (#21091) Closes #ISSUE Release Notes: - N/A --- crates/client/src/client.rs | 8 ++ crates/client/src/telemetry.rs | 4 + crates/collab/src/api.rs | 33 ++++++++ crates/collab/src/api/events.rs | 43 +++++++++- crates/collab/src/cents.rs | 3 + crates/collab/src/llm.rs | 95 +++++++++++++++++----- crates/collab/src/llm/db/queries/usages.rs | 4 +- crates/collab/src/llm/token.rs | 8 ++ crates/collab/src/rpc.rs | 9 +- crates/collab/src/tests/test_server.rs | 1 + 10 files changed, 183 insertions(+), 25 deletions(-) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 041973e884..a20584fabd 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1067,6 +1067,8 @@ impl Client { let proxy = http.proxy().cloned(); let credentials = credentials.clone(); let rpc_url = self.rpc_url(http, release_channel); + let system_id = self.telemetry.system_id(); + let metrics_id = self.telemetry.metrics_id(); cx.background_executor().spawn(async move { use HttpOrHttps::*; @@ -1118,6 +1120,12 @@ impl Client { "x-zed-release-channel", HeaderValue::from_str(release_channel.map(|r| r.dev_name()).unwrap_or("unknown"))?, ); + if let Some(system_id) = system_id { + request_headers.insert("x-zed-system-id", HeaderValue::from_str(&system_id)?); + } + if let Some(metrics_id) = metrics_id { + request_headers.insert("x-zed-metrics-id", HeaderValue::from_str(&metrics_id)?); + } match url_scheme { Https => { diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 583f9757c4..eef2a8215f 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -533,6 +533,10 @@ impl Telemetry { self.state.lock().metrics_id.clone() } + pub fn system_id(self: &Arc) -> Option> { + self.state.lock().system_id.clone() + } + pub fn installation_id(self: &Arc) -> Option> { self.state.lock().installation_id.clone() } diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 46ca5906c5..7adf17ac06 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -61,6 +61,39 @@ impl std::fmt::Display for CloudflareIpCountryHeader { } } +pub struct SystemIdHeader(String); + +impl Header for SystemIdHeader { + fn name() -> &'static HeaderName { + static SYSTEM_ID_HEADER: OnceLock = OnceLock::new(); + SYSTEM_ID_HEADER.get_or_init(|| HeaderName::from_static("x-zed-system-id")) + } + + fn decode<'i, I>(values: &mut I) -> Result + where + Self: Sized, + I: Iterator, + { + let system_id = values + .next() + .ok_or_else(axum::headers::Error::invalid)? + .to_str() + .map_err(|_| axum::headers::Error::invalid())?; + + Ok(Self(system_id.to_string())) + } + + fn encode>(&self, _values: &mut E) { + unimplemented!() + } +} + +impl std::fmt::Display for SystemIdHeader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + pub fn routes(rpc_server: Arc) -> Router<(), Body> { Router::new() .route("/user", get(get_authenticated_user)) diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 3cda6a397a..11137cb4e9 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -1578,8 +1578,8 @@ fn for_snowflake( }) } -#[derive(Serialize, Deserialize)] -struct SnowflakeRow { +#[derive(Serialize, Deserialize, Debug)] +pub struct SnowflakeRow { pub time: chrono::DateTime, pub user_id: Option, pub device_id: Option, @@ -1588,3 +1588,42 @@ struct SnowflakeRow { pub user_properties: Option, pub insert_id: Option, } + +impl SnowflakeRow { + pub fn new( + event_type: impl Into, + metrics_id: Option, + is_staff: bool, + system_id: Option, + event_properties: serde_json::Value, + ) -> Self { + Self { + time: chrono::Utc::now(), + event_type: event_type.into(), + device_id: system_id, + user_id: metrics_id.map(|id| id.to_string()), + insert_id: Some(uuid::Uuid::new_v4().to_string()), + event_properties, + user_properties: Some(json!({"is_staff": is_staff})), + } + } + + pub async fn write( + self, + client: &Option, + stream: &Option, + ) -> anyhow::Result<()> { + let Some((client, stream)) = client.as_ref().zip(stream.as_ref()) else { + return Ok(()); + }; + let row = serde_json::to_vec(&self)?; + client + .put_record() + .stream_name(stream) + .partition_key(&self.user_id.unwrap_or_default()) + .data(row.into()) + .send() + .await?; + Ok(()) + } +} diff --git a/crates/collab/src/cents.rs b/crates/collab/src/cents.rs index defbcea4e2..a05971f141 100644 --- a/crates/collab/src/cents.rs +++ b/crates/collab/src/cents.rs @@ -1,3 +1,5 @@ +use serde::Serialize; + /// A number of cents. #[derive( Debug, @@ -12,6 +14,7 @@ derive_more::AddAssign, derive_more::Sub, derive_more::SubAssign, + Serialize, )] pub struct Cents(pub u32); diff --git a/crates/collab/src/llm.rs b/crates/collab/src/llm.rs index fa48ec95ea..603b76db73 100644 --- a/crates/collab/src/llm.rs +++ b/crates/collab/src/llm.rs @@ -3,9 +3,11 @@ pub mod db; mod telemetry; mod token; +use crate::api::events::SnowflakeRow; +use crate::api::CloudflareIpCountryHeader; +use crate::build_kinesis_client; use crate::{ - api::CloudflareIpCountryHeader, build_clickhouse_client, db::UserId, executor::Executor, Cents, - Config, Error, Result, + build_clickhouse_client, db::UserId, executor::Executor, Cents, Config, Error, Result, }; use anyhow::{anyhow, Context as _}; use authorization::authorize_access_to_language_model; @@ -28,6 +30,7 @@ use rpc::{ proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME, }; use rpc::{ListModelsResponse, MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME}; +use serde_json::json; use std::{ pin::Pin, sync::Arc, @@ -45,6 +48,7 @@ pub struct LlmState { pub executor: Executor, pub db: Arc, pub http_client: ReqwestClient, + pub kinesis_client: Option, pub clickhouse_client: Option, active_user_count_by_model: RwLock, ActiveUserCount)>>, @@ -77,6 +81,11 @@ impl LlmState { executor, db, http_client, + kinesis_client: if config.kinesis_access_key.is_some() { + build_kinesis_client(&config).await.log_err() + } else { + None + }, clickhouse_client: config .clickhouse_url .as_ref() @@ -521,25 +530,50 @@ async fn check_usage_limit( UsageMeasure::TokensPerDay => "tokens_per_day", }; - if let Some(client) = state.clickhouse_client.as_ref() { - tracing::info!( - target: "user rate limit", - user_id = claims.user_id, - login = claims.github_user_login, - authn.jti = claims.jti, - is_staff = claims.is_staff, - provider = provider.to_string(), - model = model.name, - requests_this_minute = usage.requests_this_minute, - tokens_this_minute = usage.tokens_this_minute, - tokens_this_day = usage.tokens_this_day, - users_in_recent_minutes = users_in_recent_minutes, - users_in_recent_days = users_in_recent_days, - max_requests_per_minute = per_user_max_requests_per_minute, - max_tokens_per_minute = per_user_max_tokens_per_minute, - max_tokens_per_day = per_user_max_tokens_per_day, - ); + tracing::info!( + target: "user rate limit", + user_id = claims.user_id, + login = claims.github_user_login, + authn.jti = claims.jti, + is_staff = claims.is_staff, + provider = provider.to_string(), + model = model.name, + requests_this_minute = usage.requests_this_minute, + tokens_this_minute = usage.tokens_this_minute, + tokens_this_day = usage.tokens_this_day, + users_in_recent_minutes = users_in_recent_minutes, + users_in_recent_days = users_in_recent_days, + max_requests_per_minute = per_user_max_requests_per_minute, + max_tokens_per_minute = per_user_max_tokens_per_minute, + max_tokens_per_day = per_user_max_tokens_per_day, + ); + SnowflakeRow::new( + "Language Model Rate Limited", + claims.metrics_id, + claims.is_staff, + claims.system_id.clone(), + json!({ + "usage": usage, + "users_in_recent_minutes": users_in_recent_minutes, + "users_in_recent_days": users_in_recent_days, + "max_requests_per_minute": per_user_max_requests_per_minute, + "max_tokens_per_minute": per_user_max_tokens_per_minute, + "max_tokens_per_day": per_user_max_tokens_per_day, + "plan": match claims.plan { + Plan::Free => "free".to_string(), + Plan::ZedPro => "zed_pro".to_string(), + }, + "model": model.name.clone(), + "provider": provider.to_string(), + "usage_measure": resource.to_string(), + }), + ) + .write(&state.kinesis_client, &state.config.kinesis_stream) + .await + .log_err(); + + if let Some(client) = state.clickhouse_client.as_ref() { report_llm_rate_limit( client, LlmRateLimitEventRow { @@ -652,6 +686,27 @@ impl Drop for TokenCountingStream { tokens_this_minute = usage.tokens_this_minute, ); + let properties = json!({ + "plan": match claims.plan { + Plan::Free => "free".to_string(), + Plan::ZedPro => "zed_pro".to_string(), + }, + "model": model, + "provider": provider, + "usage": usage, + "tokens": tokens + }); + SnowflakeRow::new( + "Language Model Used", + claims.metrics_id, + claims.is_staff, + claims.system_id.clone(), + properties, + ) + .write(&state.kinesis_client, &state.config.kinesis_stream) + .await + .log_err(); + if let Some(clickhouse_client) = state.clickhouse_client.as_ref() { report_llm_usage( clickhouse_client, diff --git a/crates/collab/src/llm/db/queries/usages.rs b/crates/collab/src/llm/db/queries/usages.rs index f262821743..27e8039f54 100644 --- a/crates/collab/src/llm/db/queries/usages.rs +++ b/crates/collab/src/llm/db/queries/usages.rs @@ -9,7 +9,7 @@ use strum::IntoEnumIterator as _; use super::*; -#[derive(Debug, PartialEq, Clone, Copy, Default)] +#[derive(Debug, PartialEq, Clone, Copy, Default, serde::Serialize)] pub struct TokenUsage { pub input: usize, pub input_cache_creation: usize, @@ -23,7 +23,7 @@ impl TokenUsage { } } -#[derive(Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize)] pub struct Usage { pub requests_this_minute: usize, pub tokens_this_minute: usize, diff --git a/crates/collab/src/llm/token.rs b/crates/collab/src/llm/token.rs index 35f7cf26e7..7e0706e2d5 100644 --- a/crates/collab/src/llm/token.rs +++ b/crates/collab/src/llm/token.rs @@ -8,6 +8,7 @@ use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation}; use serde::{Deserialize, Serialize}; use std::time::Duration; use thiserror::Error; +use uuid::Uuid; #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -16,6 +17,10 @@ pub struct LlmTokenClaims { pub exp: u64, pub jti: String, pub user_id: u64, + #[serde(default)] + pub system_id: Option, + #[serde(default)] + pub metrics_id: Option, pub github_user_login: String, pub is_staff: bool, pub has_llm_closed_beta_feature_flag: bool, @@ -36,6 +41,7 @@ impl LlmTokenClaims { has_llm_closed_beta_feature_flag: bool, has_llm_subscription: bool, plan: rpc::proto::Plan, + system_id: Option, config: &Config, ) -> Result { let secret = config @@ -49,6 +55,8 @@ impl LlmTokenClaims { exp: (now + LLM_TOKEN_LIFETIME).timestamp() as u64, jti: uuid::Uuid::new_v4().to_string(), user_id: user.id.to_proto(), + system_id, + metrics_id: Some(user.metrics_id), github_user_login: user.github_login.clone(), is_staff, has_llm_closed_beta_feature_flag, diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 1184c48618..a17d4924b7 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -1,6 +1,6 @@ mod connection_pool; -use crate::api::CloudflareIpCountryHeader; +use crate::api::{CloudflareIpCountryHeader, SystemIdHeader}; use crate::llm::LlmTokenClaims; use crate::{ auth, @@ -137,6 +137,7 @@ struct Session { /// The GeoIP country code for the user. #[allow(unused)] geoip_country_code: Option, + system_id: Option, _executor: Executor, } @@ -682,6 +683,7 @@ impl Server { principal: Principal, zed_version: ZedVersion, geoip_country_code: Option, + system_id: Option, send_connection_id: Option>, executor: Executor, ) -> impl Future { @@ -737,6 +739,7 @@ impl Server { app_state: this.app_state.clone(), http_client, geoip_country_code, + system_id, _executor: executor.clone(), supermaven_client, }; @@ -1056,6 +1059,7 @@ pub fn routes(server: Arc) -> Router<(), Body> { .layer(Extension(server)) } +#[allow(clippy::too_many_arguments)] pub async fn handle_websocket_request( TypedHeader(ProtocolVersion(protocol_version)): TypedHeader, app_version_header: Option>, @@ -1063,6 +1067,7 @@ pub async fn handle_websocket_request( Extension(server): Extension>, Extension(principal): Extension, country_code_header: Option>, + system_id_header: Option>, ws: WebSocketUpgrade, ) -> axum::response::Response { if protocol_version != rpc::PROTOCOL_VERSION { @@ -1104,6 +1109,7 @@ pub async fn handle_websocket_request( principal, version, country_code_header.map(|header| header.to_string()), + system_id_header.map(|header| header.to_string()), None, Executor::Production, ) @@ -4053,6 +4059,7 @@ async fn get_llm_api_token( has_llm_closed_beta_feature_flag, has_llm_subscription, session.current_plan(&db).await?, + session.system_id.clone(), &session.app_state.config, )?; response.send(proto::GetLlmTokenResponse { token })?; diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 8a09f06092..c93cce9770 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -244,6 +244,7 @@ impl TestServer { Principal::User(user), ZedVersion(SemanticVersion::new(1, 0, 0)), None, + None, Some(connection_id_tx), Executor::Deterministic(cx.background_executor().clone()), )) From 43f0ea759ba69e24e9f7df3c8325352aaa226a58 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Fri, 22 Nov 2024 23:49:53 -0500 Subject: [PATCH 142/157] Remove non-existent call event types (#21093) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are not real (from Clickhouse): ``` ┌─operation────────────┬──────c─┐ 1. │ join channel │ 136221 │ 2. │ open channel notes │ 95529 │ 3. │ hang up │ 66264 │ 4. │ disable microphone │ 34116 │ 5. │ enable microphone │ 25090 │ 6. │ enable screen share │ 20751 │ 7. │ invite │ 15827 │ 8. │ share project │ 14580 │ 9. │ accept incoming │ 13708 │ 10. │ disable screen share │ 10440 │ 11. │ unshare project │ 9556 │ 12. │ decline incoming │ 455 │ 13. │ enable camera │ 6 │ 14. │ disable camera │ 4 │ └──────────────────────┴────────┘ ``` Release Notes: - N/A --- crates/collab/src/api/events.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 11137cb4e9..95bd2a89b2 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -1420,8 +1420,6 @@ fn for_snowflake( "enable screen share" => "Screen Share Enabled".to_string(), "disable screen share" => "Screen Share Disabled".to_string(), "decline incoming" => "Incoming Call Declined".to_string(), - "enable camera" => "Camera Enabled".to_string(), - "disable camera" => "Camera Disabled".to_string(), _ => format!("Unknown Call Event: {}", e.operation), }; From 8a9c53524aeb0e012e158491493a70c316a0f3ab Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Sat, 23 Nov 2024 05:38:00 +0000 Subject: [PATCH 143/157] docs: Add JSON Schema settings for json-language-server (#21084) Add json-language server docs Recognize `.vscode/*` files as JSONC by default --- assets/settings/default.json | 2 +- docs/src/languages/json.md | 43 ++++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 02527e8e67..45a211789f 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -885,7 +885,7 @@ // "file_types": { "Plain Text": ["txt"], - "JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json"], + "JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"], "Shell Script": [".env.*"] }, /// By default use a recent system version of node, or install our own. diff --git a/docs/src/languages/json.md b/docs/src/languages/json.md index 31fc8c0689..3783dcae2c 100644 --- a/docs/src/languages/json.md +++ b/docs/src/languages/json.md @@ -30,8 +30,47 @@ To workaround this behavior you can add the following to your `.prettierrc` } ``` +## JSON Language Server + +Zed automatically out of the box supports JSON Schema validation of `package.json` and `tsconfig.json` files, but `json-language-server` can use JSON Schema definitions in project files, from the [JSON Schema Store](https://www.schemastore.org/json/) or other publicly available URLs for JSON validation. + +### Inline Schema Specification + +To specify a schema inline with your JSON files, add a `$schema` top level key linking to your json schema file. + +For example to for a `.luarc.json` for use with [lua-language-server](https://github.com/LuaLS/lua-language-server/): + +```json +{ + "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", + "runtime.version": "Lua 5.4" +} +``` + +### Schema Specification via Settings + +You can alternatively associate JSON Schemas with file paths by via Zed LSP settings. + +To + +```json +"lsp": { +"json-language-server": { + "settings": { + "json": { + "schemas": [ + { + "fileMatch": ["*/*.luarc.json"], + "url": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json" + } + ] + } + } +} +``` + +You can also pass any of the [supported settings](https://github.com/Microsoft/vscode/blob/main/extensions/json-language-features/server/README.md#settings) to json-language-server by specifying them in your Zed settings.json: + From 2177e833d8caadda2e3c2f66cbb871497522cb95 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Fri, 22 Nov 2024 22:11:20 -0800 Subject: [PATCH 144/157] Upgrade jupyter websocket client (#21095) Upgrade to changes from https://github.com/runtimed/runtimed/pull/158 Release Notes: - N/A --- Cargo.lock | 114 ++++++++++++++++------ Cargo.toml | 8 +- crates/repl/src/kernels/mod.rs | 3 +- crates/repl/src/kernels/native_kernel.rs | 4 +- crates/repl/src/kernels/remote_kernels.rs | 24 ++--- 5 files changed, 98 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9881c23e84..52841152f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -885,6 +885,20 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +[[package]] +name = "async-tls" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795" +dependencies = [ + "futures-core", + "futures-io", + "rustls 0.20.9", + "rustls-pemfile 1.0.4", + "webpki", + "webpki-roots 0.22.6", +] + [[package]] name = "async-tls" version = "0.13.0" @@ -916,6 +930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce01ac37fdc85f10a43c43bc582cbd566720357011578a935761075f898baf58" dependencies = [ "async-std", + "async-tls 0.12.0", "futures-io", "futures-util", "log", @@ -930,7 +945,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e661b6cb0a6eb34d02c520b052daa3aa9ac0cc02495c9d066bbce13ead132b" dependencies = [ "async-std", - "async-tls", + "async-tls 0.13.0", "futures-io", "futures-util", "log", @@ -1100,7 +1115,7 @@ dependencies = [ "fastrand 2.2.0", "hex", "http 0.2.12", - "ring", + "ring 0.17.8", "time", "tokio", "tracing", @@ -1290,7 +1305,7 @@ dependencies = [ "once_cell", "p256", "percent-encoding", - "ring", + "ring 0.17.8", "sha2", "subtle", "time", @@ -4457,7 +4472,7 @@ dependencies = [ "futures-core", "futures-sink", "nanorand", - "spin", + "spin 0.9.8", ] [[package]] @@ -6370,7 +6385,7 @@ dependencies = [ "base64 0.21.7", "js-sys", "pem", - "ring", + "ring 0.17.8", "serde", "serde_json", "simple_asn1", @@ -6378,9 +6393,9 @@ dependencies = [ [[package]] name = "jupyter-protocol" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f3e9d36f282f7e0400de20921d283121a97c5a5a6db2c1bb0c0853defff9934" +checksum = "3d4d496ac890e14efc12c5289818b3c39e3026a7bb02d5576b011e1a062d4bcc" dependencies = [ "anyhow", "async-trait", @@ -6396,9 +6411,9 @@ dependencies = [ [[package]] name = "jupyter-serde" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11adb69edaf2eb03d5e84249f68f870dd03d4c8f955314b5a32b2db5798e9b9a" +checksum = "32aa595c3912167b7eafcaa822b767ad1fa9605a18127fc9ac741241b796410e" dependencies = [ "anyhow", "serde", @@ -6409,9 +6424,9 @@ dependencies = [ [[package]] name = "jupyter-websocket-client" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d315d037789a652084877b0919615e937d2f2e877b01aa4ba8fcc1ab07cb58b" +checksum = "5850894210a3f033ff730d6f956b0335db38573ce7bb61c6abbf69dcbe284ba7" dependencies = [ "anyhow", "async-trait", @@ -6717,7 +6732,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin", + "spin 0.9.8", ] [[package]] @@ -7439,9 +7454,9 @@ dependencies = [ [[package]] name = "nbformat" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187de1b1f1430353ef9b5208096d84f7bf089ee1593f14213d122b7fbb1f3dee" +checksum = "aa6827a3881aa100bb2241cd2633b3c79474dbc93704f1f2cf5cc85064cda4be" dependencies = [ "anyhow", "chrono", @@ -9470,7 +9485,7 @@ dependencies = [ "bytes 1.8.0", "getrandom 0.2.15", "rand 0.8.5", - "ring", + "ring 0.17.8", "rustc-hash 2.0.0", "rustls 0.23.16", "rustls-pki-types", @@ -10113,6 +10128,21 @@ dependencies = [ "util", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + [[package]] name = "ring" version = "0.17.8" @@ -10123,8 +10153,8 @@ dependencies = [ "cfg-if", "getrandom 0.2.15", "libc", - "spin", - "untrusted", + "spin 0.9.8", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -10260,9 +10290,9 @@ dependencies = [ [[package]] name = "runtimelib" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2db079f82c110e25c3202d20c7cd29dcbfa93d96de7c5bb8bb6f294f477567cf" +checksum = "b3a8ab675beb5cf25c28f9c6ddb8f47bcf73b43872797e6ab6157865f44d1e19" dependencies = [ "anyhow", "async-dispatcher", @@ -10276,7 +10306,7 @@ dependencies = [ "glob", "jupyter-protocol", "jupyter-serde", - "ring", + "ring 0.17.8", "serde", "serde_json", "shellexpand 3.1.0", @@ -10403,6 +10433,18 @@ dependencies = [ "rustix 0.38.40", ] +[[package]] +name = "rustls" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring 0.16.20", + "sct", + "webpki", +] + [[package]] name = "rustls" version = "0.21.12" @@ -10410,7 +10452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring", + "ring 0.17.8", "rustls-webpki 0.101.7", "sct", ] @@ -10422,7 +10464,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" dependencies = [ "once_cell", - "ring", + "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle", @@ -10487,8 +10529,8 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring", - "untrusted", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] @@ -10497,9 +10539,9 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring", + "ring 0.17.8", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -10613,8 +10655,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] @@ -11376,6 +11418,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.8" @@ -13485,6 +13533,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -14393,8 +14447,8 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring", - "untrusted", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b071ca19d1..c066b942e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -378,14 +378,14 @@ indexmap = { version = "1.6.2", features = ["serde"] } indoc = "2" itertools = "0.13.0" jsonwebtoken = "9.3" -jupyter-protocol = { version = "0.2.0" } -jupyter-websocket-client = { version = "0.4.1" } +jupyter-protocol = { version = "0.3.0" } +jupyter-websocket-client = { version = "0.5.0" } libc = "0.2" linkify = "0.10.0" log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] } markup5ever_rcdom = "0.3.0" nanoid = "0.4" -nbformat = "0.6.0" +nbformat = { version = "0.7.0" } nix = "0.29" num-format = "0.4.4" once_cell = "1.19.0" @@ -419,7 +419,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f "stream", ] } rsa = "0.9.6" -runtimelib = { version = "0.21.0", default-features = false, features = [ +runtimelib = { version = "0.22.0", default-features = false, features = [ "async-dispatcher-runtime", ] } rustc-demangle = "0.1.23" diff --git a/crates/repl/src/kernels/mod.rs b/crates/repl/src/kernels/mod.rs index 47fde97154..e829b1946c 100644 --- a/crates/repl/src/kernels/mod.rs +++ b/crates/repl/src/kernels/mod.rs @@ -15,7 +15,8 @@ use project::{Project, WorktreeId}; pub use remote_kernels::*; use anyhow::Result; -use runtimelib::{ExecutionState, JupyterKernelspec, JupyterMessage, KernelInfoReply}; +use jupyter_protocol::JupyterKernelspec; +use runtimelib::{ExecutionState, JupyterMessage, KernelInfoReply}; use ui::{Icon, IconName, SharedString}; pub type JupyterMessageChannel = stream::SelectAll>; diff --git a/crates/repl/src/kernels/native_kernel.rs b/crates/repl/src/kernels/native_kernel.rs index 6f7c5d92ee..974a721ac5 100644 --- a/crates/repl/src/kernels/native_kernel.rs +++ b/crates/repl/src/kernels/native_kernel.rs @@ -6,9 +6,9 @@ use futures::{ AsyncBufReadExt as _, SinkExt as _, }; use gpui::{EntityId, Task, View, WindowContext}; -use jupyter_protocol::{JupyterMessage, JupyterMessageContent, KernelInfoReply}; +use jupyter_protocol::{JupyterKernelspec, JupyterMessage, JupyterMessageContent, KernelInfoReply}; use project::Fs; -use runtimelib::{dirs, ConnectionInfo, ExecutionState, JupyterKernelspec}; +use runtimelib::{dirs, ConnectionInfo, ExecutionState}; use smol::{net::TcpListener, process::Command}; use std::{ env, diff --git a/crates/repl/src/kernels/remote_kernels.rs b/crates/repl/src/kernels/remote_kernels.rs index 808a7dbf02..e1b41276fa 100644 --- a/crates/repl/src/kernels/remote_kernels.rs +++ b/crates/repl/src/kernels/remote_kernels.rs @@ -1,8 +1,7 @@ use futures::{channel::mpsc, SinkExt as _}; use gpui::{Task, View, WindowContext}; use http_client::{AsyncBody, HttpClient, Request}; -use jupyter_protocol::{ExecutionState, JupyterMessage, KernelInfoReply}; -use runtimelib::JupyterKernelspec; +use jupyter_protocol::{ExecutionState, JupyterKernelspec, JupyterMessage, KernelInfoReply}; use futures::StreamExt; use smol::io::AsyncReadExt as _; @@ -34,8 +33,9 @@ pub async fn launch_remote_kernel( // let kernel_launch_request = KernelLaunchRequest { name: kernel_name.to_string(), - // todo: add path to runtimelib - // path, + // Note: since the path we have locally may not be the same as the one on the remote server, + // we don't send it. We'll have to evaluate this decisiion along the way. + path: None, }; let kernel_launch_request = serde_json::to_string(&kernel_launch_request)?; @@ -91,19 +91,7 @@ pub async fn list_remote_kernelspecs( name: name.clone(), url: remote_server.base_url.clone(), token: remote_server.token.clone(), - // todo: line up the jupyter kernelspec from runtimelib with - // the kernelspec pulled from the API - // - // There are _small_ differences, so we may just want a impl `From` - kernelspec: JupyterKernelspec { - argv: spec.spec.argv, - display_name: spec.spec.display_name, - language: spec.spec.language, - // todo: fix up mismatch in types here - metadata: None, - interrupt_mode: None, - env: None, - }, + kernelspec: spec.spec, }) .collect::>(); @@ -163,7 +151,7 @@ impl RemoteRunningKernel { ) .await?; - let kernel_socket = remote_server.connect_to_kernel(&kernel_id).await?; + let (kernel_socket, _response) = remote_server.connect_to_kernel(&kernel_id).await?; let (mut w, mut r): (JupyterWebSocketWriter, JupyterWebSocketReader) = kernel_socket.split(); From f30de4852a5c672b4cb43ab528156c953f1c6eb0 Mon Sep 17 00:00:00 2001 From: Peter Tripp Date: Sat, 23 Nov 2024 06:17:39 +0000 Subject: [PATCH 145/157] docs: Proto Language is by extension not native (#21096) Fixes docs to reflect that Protobuf support is via extension. Comment out references ProtoLS formatter. Need to test both protols and protobuf-language-server to ensure both work. --- docs/src/languages/proto.md | 40 +++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/docs/src/languages/proto.md b/docs/src/languages/proto.md index 934080a1d7..777fd81b8a 100644 --- a/docs/src/languages/proto.md +++ b/docs/src/languages/proto.md @@ -1,9 +1,44 @@ # Proto -Proto/proto3 (Protocol Buffers definition language) support is available natively in Zed. +Proto/proto3 (Protocol Buffers definition language) support is available through the [Proto extension](https://github.com/zed-industries/zed/tree/main/extensions/proto). - Tree Sitter: [coder3101/tree-sitter-proto](https://github.com/coder3101/tree-sitter-proto) -- Language Server: [protols](https://github.com/coder3101/protols) +- Language Servers: [protobuf-language-server](https://github.com/lasorda/protobuf-language-server) + + From 9adc3b4e82f1c3e825c597bedeb0b184876dcb78 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Sat, 23 Nov 2024 11:24:52 -0500 Subject: [PATCH 146/157] Break ground on `assistant2` (#21109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR breaks ground on a new `assistant2` crate. In order to see this new version of the assistant, both of the following must be true: 1. The `assistant2` feature flag is enabled for your user - It is **not** currently enabled for all staff. 2. You are running a development build of Zed The intent here is to enable the folks working on `assistant2` to incrementally land work onto `main` without breaking use of the current Assistant for anyone. Screenshot 2024-11-23 at 10 46 08 AM Release Notes: - N/A --- Cargo.lock | 14 +++ Cargo.toml | 2 + crates/assistant2/Cargo.toml | 22 ++++ crates/assistant2/LICENSE-GPL | 1 + crates/assistant2/src/assistant.rs | 40 +++++++ crates/assistant2/src/assistant_panel.rs | 123 ++++++++++++++++++++++ crates/feature_flags/src/feature_flags.rs | 10 ++ crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 1 + crates/zed/src/zed.rs | 34 +++++- 10 files changed, 243 insertions(+), 5 deletions(-) create mode 100644 crates/assistant2/Cargo.toml create mode 120000 crates/assistant2/LICENSE-GPL create mode 100644 crates/assistant2/src/assistant.rs create mode 100644 crates/assistant2/src/assistant_panel.rs diff --git a/Cargo.lock b/Cargo.lock index 52841152f0..8138744707 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -449,6 +449,19 @@ dependencies = [ "zed_actions", ] +[[package]] +name = "assistant2" +version = "0.1.0" +dependencies = [ + "anyhow", + "command_palette_hooks", + "feature_flags", + "gpui", + "proto", + "ui", + "workspace", +] + [[package]] name = "assistant_slash_command" version = "0.1.0" @@ -15549,6 +15562,7 @@ dependencies = [ "ashpd", "assets", "assistant", + "assistant2", "async-watch", "audio", "auto_update", diff --git a/Cargo.toml b/Cargo.toml index c066b942e7..a8537611fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "crates/anthropic", "crates/assets", "crates/assistant", + "crates/assistant2", "crates/assistant_slash_command", "crates/assistant_tool", "crates/audio", @@ -186,6 +187,7 @@ ai = { path = "crates/ai" } anthropic = { path = "crates/anthropic" } assets = { path = "crates/assets" } assistant = { path = "crates/assistant" } +assistant2 = { path = "crates/assistant2" } assistant_slash_command = { path = "crates/assistant_slash_command" } assistant_tool = { path = "crates/assistant_tool" } audio = { path = "crates/audio" } diff --git a/crates/assistant2/Cargo.toml b/crates/assistant2/Cargo.toml new file mode 100644 index 0000000000..320cd015e2 --- /dev/null +++ b/crates/assistant2/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "assistant2" +version = "0.1.0" +edition = "2021" +publish = false +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/assistant.rs" +doctest = false + +[dependencies] +anyhow.workspace = true +command_palette_hooks.workspace = true +feature_flags.workspace = true +gpui.workspace = true +proto.workspace = true +ui.workspace = true +workspace.workspace = true diff --git a/crates/assistant2/LICENSE-GPL b/crates/assistant2/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/assistant2/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/assistant2/src/assistant.rs b/crates/assistant2/src/assistant.rs new file mode 100644 index 0000000000..31676198ba --- /dev/null +++ b/crates/assistant2/src/assistant.rs @@ -0,0 +1,40 @@ +mod assistant_panel; + +use command_palette_hooks::CommandPaletteFilter; +use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt}; +use gpui::{actions, AppContext}; + +pub use crate::assistant_panel::AssistantPanel; + +actions!(assistant2, [ToggleFocus, NewChat]); + +const NAMESPACE: &str = "assistant2"; + +/// Initializes the `assistant2` crate. +pub fn init(cx: &mut AppContext) { + assistant_panel::init(cx); + feature_gate_assistant2_actions(cx); +} + +fn feature_gate_assistant2_actions(cx: &mut AppContext) { + const ASSISTANT1_NAMESPACE: &str = "assistant"; + + CommandPaletteFilter::update_global(cx, |filter, _cx| { + filter.hide_namespace(NAMESPACE); + }); + + cx.observe_flag::(move |is_enabled, cx| { + if is_enabled { + CommandPaletteFilter::update_global(cx, |filter, _cx| { + filter.show_namespace(NAMESPACE); + filter.hide_namespace(ASSISTANT1_NAMESPACE); + }); + } else { + CommandPaletteFilter::update_global(cx, |filter, _cx| { + filter.hide_namespace(NAMESPACE); + filter.show_namespace(ASSISTANT1_NAMESPACE); + }); + } + }) + .detach(); +} diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs new file mode 100644 index 0000000000..7c586dd35e --- /dev/null +++ b/crates/assistant2/src/assistant_panel.rs @@ -0,0 +1,123 @@ +use anyhow::Result; +use gpui::{ + prelude::*, px, Action, AppContext, AsyncWindowContext, EventEmitter, FocusHandle, + FocusableView, Pixels, Task, View, ViewContext, WeakView, WindowContext, +}; +use ui::prelude::*; +use workspace::dock::{DockPosition, Panel, PanelEvent}; +use workspace::{Pane, Workspace}; + +use crate::{NewChat, ToggleFocus}; + +pub fn init(cx: &mut AppContext) { + cx.observe_new_views( + |workspace: &mut Workspace, _cx: &mut ViewContext| { + workspace.register_action(|workspace, _: &ToggleFocus, cx| { + workspace.toggle_panel_focus::(cx); + }); + }, + ) + .detach(); +} + +pub struct AssistantPanel { + pane: View, +} + +impl AssistantPanel { + pub fn load( + workspace: WeakView, + cx: AsyncWindowContext, + ) -> Task>> { + cx.spawn(|mut cx| async move { + workspace.update(&mut cx, |workspace, cx| { + cx.new_view(|cx| Self::new(workspace, cx)) + }) + }) + } + + fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { + let pane = cx.new_view(|cx| { + let mut pane = Pane::new( + workspace.weak_handle(), + workspace.project().clone(), + Default::default(), + None, + NewChat.boxed_clone(), + cx, + ); + pane.set_can_split(false, cx); + pane.set_can_navigate(true, cx); + + pane + }); + + Self { pane } + } +} + +impl FocusableView for AssistantPanel { + fn focus_handle(&self, cx: &AppContext) -> FocusHandle { + self.pane.focus_handle(cx) + } +} + +impl EventEmitter for AssistantPanel {} + +impl Panel for AssistantPanel { + fn persistent_name() -> &'static str { + "AssistantPanel2" + } + + fn position(&self, _cx: &WindowContext) -> DockPosition { + DockPosition::Right + } + + fn position_is_valid(&self, _: DockPosition) -> bool { + true + } + + fn set_position(&mut self, _position: DockPosition, _cx: &mut ViewContext) {} + + fn size(&self, _cx: &WindowContext) -> Pixels { + px(640.) + } + + fn set_size(&mut self, _size: Option, _cx: &mut ViewContext) {} + + fn is_zoomed(&self, cx: &WindowContext) -> bool { + self.pane.read(cx).is_zoomed() + } + + fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext) { + self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx)); + } + + fn set_active(&mut self, _active: bool, _cx: &mut ViewContext) {} + + fn pane(&self) -> Option> { + Some(self.pane.clone()) + } + + fn remote_id() -> Option { + Some(proto::PanelId::AssistantPanel) + } + + fn icon(&self, _cx: &WindowContext) -> Option { + Some(IconName::ZedAssistant) + } + + fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { + Some("Assistant Panel") + } + + fn toggle_action(&self) -> Box { + Box::new(ToggleFocus) + } +} + +impl Render for AssistantPanel { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + div().child(Label::new("Assistant II")) + } +} diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs index 286acdfc98..416971b36e 100644 --- a/crates/feature_flags/src/feature_flags.rs +++ b/crates/feature_flags/src/feature_flags.rs @@ -39,6 +39,16 @@ pub trait FeatureFlag { } } +pub struct Assistant2FeatureFlag; + +impl FeatureFlag for Assistant2FeatureFlag { + const NAME: &'static str = "assistant2"; + + fn enabled_for_staff() -> bool { + false + } +} + pub struct Remoting {} impl FeatureFlag for Remoting { const NAME: &'static str = "remoting"; diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 755076e360..1959fb0e00 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -19,6 +19,7 @@ activity_indicator.workspace = true anyhow.workspace = true assets.workspace = true assistant.workspace = true +assistant2.workspace = true async-watch.workspace = true audio.workspace = true auto_update.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 73b0e0f199..6febe05d10 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -406,6 +406,7 @@ fn main() { stdout_is_a_pty(), cx, ); + assistant2::init(cx); assistant_hints::init(cx); repl::init( app_state.fs.clone(), diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 322ea3610b..086935542c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -236,10 +236,29 @@ pub fn initialize_workspace( .unwrap_or(true) }); + let release_channel = ReleaseChannel::global(cx); + let assistant2_feature_flag = cx.wait_for_flag::(); + let prompt_builder = prompt_builder.clone(); cx.spawn(|workspace_handle, mut cx| async move { - let assistant_panel = - assistant::AssistantPanel::load(workspace_handle.clone(), prompt_builder, cx.clone()); + let is_assistant2_enabled = if cfg!(test) { + false + } else { + let is_assistant2_feature_flag_enabled = assistant2_feature_flag.await; + release_channel == ReleaseChannel::Dev && is_assistant2_feature_flag_enabled + }; + + let (assistant_panel, assistant2_panel) = if is_assistant2_enabled { + let assistant2_panel = + assistant2::AssistantPanel::load(workspace_handle.clone(), cx.clone()).await?; + + (None, Some(assistant2_panel)) + } else { + let assistant_panel = + assistant::AssistantPanel::load(workspace_handle.clone(), prompt_builder, cx.clone()).await?; + + (Some(assistant_panel), None) + }; let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone()); let outline_panel = OutlinePanel::load(workspace_handle.clone(), cx.clone()); @@ -257,7 +276,6 @@ pub fn initialize_workspace( project_panel, outline_panel, terminal_panel, - assistant_panel, channels_panel, chat_panel, notification_panel, @@ -265,14 +283,20 @@ pub fn initialize_workspace( project_panel, outline_panel, terminal_panel, - assistant_panel, channels_panel, chat_panel, notification_panel, )?; workspace_handle.update(&mut cx, |workspace, cx| { - workspace.add_panel(assistant_panel, cx); + if let Some(assistant_panel) = assistant_panel { + workspace.add_panel(assistant_panel, cx); + } + + if let Some(assistant2_panel) = assistant2_panel { + workspace.add_panel(assistant2_panel, cx); + } + workspace.add_panel(project_panel, cx); workspace.add_panel(outline_panel, cx); workspace.add_panel(terminal_panel, cx); From 3a0408953d25f3cbb16902a021e1c699d6883b18 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Sat, 23 Nov 2024 12:11:31 -0500 Subject: [PATCH 147/157] Factor out language model selector into its own crate (#21113) This PR factors the language model selector out into its own `language_model_selector` crate so that it can be reused in `assistant2`. Also renamed it from `ModelSelector` to `LanguageModelSelector` to be a bit more specific. Release Notes: - N/A --- Cargo.lock | 15 +++++ Cargo.toml | 2 + crates/assistant/Cargo.toml | 1 + crates/assistant/src/assistant.rs | 2 - crates/assistant/src/assistant_panel.rs | 26 ++++++--- crates/assistant/src/inline_assistant.rs | 20 +++++-- .../src/terminal_inline_assistant.rs | 21 +++++-- crates/language_model_selector/Cargo.toml | 22 +++++++ crates/language_model_selector/LICENSE-GPL | 1 + .../src/language_model_selector.rs} | 57 ++++++++++--------- 10 files changed, 119 insertions(+), 48 deletions(-) create mode 100644 crates/language_model_selector/Cargo.toml create mode 120000 crates/language_model_selector/LICENSE-GPL rename crates/{assistant/src/model_selector.rs => language_model_selector/src/language_model_selector.rs} (90%) diff --git a/Cargo.lock b/Cargo.lock index 8138744707..cb2e662311 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -402,6 +402,7 @@ dependencies = [ "indoc", "language", "language_model", + "language_model_selector", "language_models", "languages", "log", @@ -6603,6 +6604,20 @@ dependencies = [ "util", ] +[[package]] +name = "language_model_selector" +version = "0.1.0" +dependencies = [ + "feature_flags", + "gpui", + "language_model", + "picker", + "proto", + "ui", + "workspace", + "zed_actions", +] + [[package]] name = "language_models" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index a8537611fe..2e5111e2ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "crates/language", "crates/language_extension", "crates/language_model", + "crates/language_model_selector", "crates/language_models", "crates/language_selector", "crates/language_tools", @@ -236,6 +237,7 @@ journal = { path = "crates/journal" } language = { path = "crates/language" } language_extension = { path = "crates/language_extension" } language_model = { path = "crates/language_model" } +language_model_selector = { path = "crates/language_model_selector" } language_models = { path = "crates/language_models" } language_selector = { path = "crates/language_selector" } language_tools = { path = "crates/language_tools" } diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 7f5aef3f46..0799d4bbdb 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -50,6 +50,7 @@ indexed_docs.workspace = true indoc.workspace = true language.workspace = true language_model.workspace = true +language_model_selector.workspace = true language_models.workspace = true log.workspace = true lsp.workspace = true diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index b891c3da2a..f6e435bfb8 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -5,7 +5,6 @@ pub mod assistant_settings; mod context; pub mod context_store; mod inline_assistant; -mod model_selector; mod patch; mod prompt_library; mod prompts; @@ -37,7 +36,6 @@ pub(crate) use inline_assistant::*; use language_model::{ LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage, }; -pub(crate) use model_selector::*; pub use patch::*; pub use prompts::PromptBuilder; use prompts::PromptLoadingParams; diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index ff60f2b918..9a7beb96d2 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -17,9 +17,9 @@ use crate::{ ContextEvent, ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole, DeployHistory, DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles, InsertIntoEditor, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, - MessageMetadata, MessageStatus, ModelPickerDelegate, ModelSelector, NewContext, - ParsedSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, - RequestType, SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector, + MessageMetadata, MessageStatus, NewContext, ParsedSlashCommand, PendingSlashCommandStatus, + QuoteSelection, RemoteContextMetadata, RequestType, SavedContextMetadata, Split, ToggleFocus, + ToggleModelSelector, }; use anyhow::Result; use assistant_slash_command::{SlashCommand, SlashCommandOutputSection}; @@ -55,6 +55,7 @@ use language_model::{ LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role, ZED_CLOUD_PROVIDER_ID, }; +use language_model_selector::{LanguageModelPickerDelegate, LanguageModelSelector}; use multi_buffer::MultiBufferRow; use picker::{Picker, PickerDelegate}; use project::lsp_store::LocalLspAdapterDelegate; @@ -142,7 +143,7 @@ pub struct AssistantPanel { languages: Arc, fs: Arc, subscriptions: Vec, - model_selector_menu_handle: PopoverMenuHandle>, + model_selector_menu_handle: PopoverMenuHandle>, model_summary_editor: View, authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>, configuration_subscription: Option, @@ -4457,13 +4458,13 @@ pub struct ContextEditorToolbarItem { fs: Arc, active_context_editor: Option>, model_summary_editor: View, - model_selector_menu_handle: PopoverMenuHandle>, + model_selector_menu_handle: PopoverMenuHandle>, } impl ContextEditorToolbarItem { pub fn new( workspace: &Workspace, - model_selector_menu_handle: PopoverMenuHandle>, + model_selector_menu_handle: PopoverMenuHandle>, model_summary_editor: View, ) -> Self { Self { @@ -4559,8 +4560,17 @@ impl Render for ContextEditorToolbarItem { // .map(|remaining_items| format!("Files to scan: {}", remaining_items)) // }) .child( - ModelSelector::new( - self.fs.clone(), + LanguageModelSelector::new( + { + let fs = self.fs.clone(); + move |model, cx| { + update_settings_file::( + fs.clone(), + cx, + move |settings, _| settings.set_model(model.clone()), + ); + } + }, ButtonLike::new("active-model") .style(ButtonStyle::Subtle) .child( diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index 855972c267..b1cb1d81b4 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -1,7 +1,7 @@ use crate::{ assistant_settings::AssistantSettings, humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, CharOperation, CycleNextInlineAssist, - CyclePreviousInlineAssist, LineDiff, LineOperation, ModelSelector, RequestType, StreamingDiff, + CyclePreviousInlineAssist, LineDiff, LineOperation, RequestType, StreamingDiff, }; use anyhow::{anyhow, Context as _, Result}; use client::{telemetry::Telemetry, ErrorExt}; @@ -33,12 +33,13 @@ use language_model::{ LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, LanguageModelTextStream, Role, }; +use language_model_selector::LanguageModelSelector; use language_models::report_assistant_event; use multi_buffer::MultiBufferRow; use parking_lot::Mutex; use project::{CodeAction, ProjectTransaction}; use rope::Rope; -use settings::{Settings, SettingsStore}; +use settings::{update_settings_file, Settings, SettingsStore}; use smol::future::FutureExt; use std::{ cmp, @@ -1500,8 +1501,17 @@ impl Render for PromptEditor { .justify_center() .gap_2() .child( - ModelSelector::new( - self.fs.clone(), + LanguageModelSelector::new( + { + let fs = self.fs.clone(); + move |model, cx| { + update_settings_file::( + fs.clone(), + cx, + move |settings, _| settings.set_model(model.clone()), + ); + } + }, IconButton::new("context", IconName::SettingsAlt) .shape(IconButtonShape::Square) .icon_size(IconSize::Small) @@ -1521,7 +1531,7 @@ impl Render for PromptEditor { ) }), ) - .with_info_text( + .info_text( "Inline edits use context\n\ from the currently selected\n\ assistant panel tab.", diff --git a/crates/assistant/src/terminal_inline_assistant.rs b/crates/assistant/src/terminal_inline_assistant.rs index 51738b90e4..a5424a8d7e 100644 --- a/crates/assistant/src/terminal_inline_assistant.rs +++ b/crates/assistant/src/terminal_inline_assistant.rs @@ -1,6 +1,7 @@ +use crate::assistant_settings::AssistantSettings; use crate::{ - humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, - ModelSelector, RequestType, DEFAULT_CONTEXT_LINES, + humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, RequestType, + DEFAULT_CONTEXT_LINES, }; use anyhow::{Context as _, Result}; use client::telemetry::Telemetry; @@ -19,8 +20,9 @@ use language::Buffer; use language_model::{ LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role, }; +use language_model_selector::LanguageModelSelector; use language_models::report_assistant_event; -use settings::Settings; +use settings::{update_settings_file, Settings}; use std::{ cmp, sync::Arc, @@ -612,8 +614,17 @@ impl Render for PromptEditor { .w_12() .justify_center() .gap_2() - .child(ModelSelector::new( - self.fs.clone(), + .child(LanguageModelSelector::new( + { + let fs = self.fs.clone(); + move |model, cx| { + update_settings_file::( + fs.clone(), + cx, + move |settings, _| settings.set_model(model.clone()), + ); + } + }, IconButton::new("context", IconName::SettingsAlt) .shape(IconButtonShape::Square) .icon_size(IconSize::Small) diff --git a/crates/language_model_selector/Cargo.toml b/crates/language_model_selector/Cargo.toml new file mode 100644 index 0000000000..cd00af50c0 --- /dev/null +++ b/crates/language_model_selector/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "language_model_selector" +version = "0.1.0" +edition = "2021" +publish = false +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/language_model_selector.rs" + +[dependencies] +feature_flags.workspace = true +gpui.workspace = true +language_model.workspace = true +picker.workspace = true +proto.workspace = true +ui.workspace = true +workspace.workspace = true +zed_actions.workspace = true diff --git a/crates/language_model_selector/LICENSE-GPL b/crates/language_model_selector/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/language_model_selector/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/assistant/src/model_selector.rs b/crates/language_model_selector/src/language_model_selector.rs similarity index 90% rename from crates/assistant/src/model_selector.rs rename to crates/language_model_selector/src/language_model_selector.rs index 1b26b8b5ad..562bccbd88 100644 --- a/crates/assistant/src/model_selector.rs +++ b/crates/language_model_selector/src/language_model_selector.rs @@ -1,30 +1,27 @@ -use feature_flags::ZedPro; - -use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry}; -use proto::Plan; -use workspace::ShowConfiguration; - use std::sync::Arc; -use crate::assistant_settings::AssistantSettings; -use fs::Fs; -use gpui::{Action, AnyElement, DismissEvent, SharedString, Task}; +use feature_flags::ZedPro; +use gpui::{Action, AnyElement, AppContext, DismissEvent, SharedString, Task}; +use language_model::{LanguageModel, LanguageModelAvailability, LanguageModelRegistry}; use picker::{Picker, PickerDelegate}; -use settings::update_settings_file; +use proto::Plan; use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger}; +use workspace::ShowConfiguration; const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro"; +type OnModelChanged = Arc, &AppContext) + 'static>; + #[derive(IntoElement)] -pub struct ModelSelector { - handle: Option>>, - fs: Arc, +pub struct LanguageModelSelector { + handle: Option>>, + on_model_changed: OnModelChanged, trigger: T, info_text: Option, } -pub struct ModelPickerDelegate { - fs: Arc, +pub struct LanguageModelPickerDelegate { + on_model_changed: OnModelChanged, all_models: Vec, filtered_models: Vec, selected_index: usize, @@ -38,28 +35,34 @@ struct ModelInfo { is_selected: bool, } -impl ModelSelector { - pub fn new(fs: Arc, trigger: T) -> Self { - ModelSelector { +impl LanguageModelSelector { + pub fn new( + on_model_changed: impl Fn(Arc, &AppContext) + 'static, + trigger: T, + ) -> Self { + LanguageModelSelector { handle: None, - fs, + on_model_changed: Arc::new(on_model_changed), trigger, info_text: None, } } - pub fn with_handle(mut self, handle: PopoverMenuHandle>) -> Self { + pub fn with_handle( + mut self, + handle: PopoverMenuHandle>, + ) -> Self { self.handle = Some(handle); self } - pub fn with_info_text(mut self, text: impl Into) -> Self { + pub fn info_text(mut self, text: impl Into) -> Self { self.info_text = Some(text.into()); self } } -impl PickerDelegate for ModelPickerDelegate { +impl PickerDelegate for LanguageModelPickerDelegate { type ListItem = ListItem; fn match_count(&self) -> usize { @@ -137,9 +140,7 @@ impl PickerDelegate for ModelPickerDelegate { fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext>) { if let Some(model_info) = self.filtered_models.get(self.selected_index) { let model = model_info.model.clone(); - update_settings_file::(self.fs.clone(), cx, move |settings, _| { - settings.set_model(model.clone()) - }); + (self.on_model_changed)(model.clone(), cx); // Update the selection status let selected_model_id = model_info.model.id(); @@ -296,7 +297,7 @@ impl PickerDelegate for ModelPickerDelegate { } } -impl RenderOnce for ModelSelector { +impl RenderOnce for LanguageModelSelector { fn render(self, cx: &mut WindowContext) -> impl IntoElement { let selected_provider = LanguageModelRegistry::read_global(cx) .active_provider() @@ -331,8 +332,8 @@ impl RenderOnce for ModelSelector { }) .collect::>(); - let delegate = ModelPickerDelegate { - fs: self.fs.clone(), + let delegate = LanguageModelPickerDelegate { + on_model_changed: self.on_model_changed.clone(), all_models: all_models.clone(), filtered_models: all_models, selected_index: 0, From 2a23db6e052e1262b65f57d3a88ec732beda1337 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Sat, 23 Nov 2024 12:46:11 -0500 Subject: [PATCH 148/157] assistant2: Sketch in toolbar (#21114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR sketches in the toolbar for `assistant2`. Screenshot 2024-11-23 at 12 39 49 PM Release Notes: - N/A --- Cargo.lock | 2 + crates/assistant2/Cargo.toml | 2 + crates/assistant2/src/assistant.rs | 2 +- crates/assistant2/src/assistant_panel.rs | 127 ++++++++++++++++++++++- 4 files changed, 127 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb2e662311..2a5c4d94bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -458,6 +458,8 @@ dependencies = [ "command_palette_hooks", "feature_flags", "gpui", + "language_model", + "language_model_selector", "proto", "ui", "workspace", diff --git a/crates/assistant2/Cargo.toml b/crates/assistant2/Cargo.toml index 320cd015e2..8e3405c340 100644 --- a/crates/assistant2/Cargo.toml +++ b/crates/assistant2/Cargo.toml @@ -17,6 +17,8 @@ anyhow.workspace = true command_palette_hooks.workspace = true feature_flags.workspace = true gpui.workspace = true +language_model.workspace = true +language_model_selector.workspace = true proto.workspace = true ui.workspace = true workspace.workspace = true diff --git a/crates/assistant2/src/assistant.rs b/crates/assistant2/src/assistant.rs index 31676198ba..da1038f2ca 100644 --- a/crates/assistant2/src/assistant.rs +++ b/crates/assistant2/src/assistant.rs @@ -6,7 +6,7 @@ use gpui::{actions, AppContext}; pub use crate::assistant_panel::AssistantPanel; -actions!(assistant2, [ToggleFocus, NewChat]); +actions!(assistant2, [ToggleFocus, NewChat, ToggleModelSelector]); const NAMESPACE: &str = "assistant2"; diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs index 7c586dd35e..51bfb8c4ff 100644 --- a/crates/assistant2/src/assistant_panel.rs +++ b/crates/assistant2/src/assistant_panel.rs @@ -3,11 +3,13 @@ use gpui::{ prelude::*, px, Action, AppContext, AsyncWindowContext, EventEmitter, FocusHandle, FocusableView, Pixels, Task, View, ViewContext, WeakView, WindowContext, }; -use ui::prelude::*; +use language_model::LanguageModelRegistry; +use language_model_selector::LanguageModelSelector; +use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, Tab, Tooltip}; use workspace::dock::{DockPosition, Panel, PanelEvent}; use workspace::{Pane, Workspace}; -use crate::{NewChat, ToggleFocus}; +use crate::{NewChat, ToggleFocus, ToggleModelSelector}; pub fn init(cx: &mut AppContext) { cx.observe_new_views( @@ -116,8 +118,123 @@ impl Panel for AssistantPanel { } } -impl Render for AssistantPanel { - fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - div().child(Label::new("Assistant II")) +impl AssistantPanel { + fn render_toolbar(&self, cx: &mut ViewContext) -> impl IntoElement { + let focus_handle = self.focus_handle(cx); + + h_flex() + .id("assistant-toolbar") + .justify_between() + .gap(DynamicSpacing::Base08.rems(cx)) + .h(Tab::container_height(cx)) + .px(DynamicSpacing::Base08.rems(cx)) + .bg(cx.theme().colors().tab_bar_background) + .border_b_1() + .border_color(cx.theme().colors().border_variant) + .child(h_flex().child(Label::new("Chat Title Goes Here"))) + .child( + h_flex() + .gap(DynamicSpacing::Base08.rems(cx)) + .child(self.render_language_model_selector(cx)) + .child(Divider::vertical()) + .child( + IconButton::new("new-chat", IconName::Plus) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .style(ButtonStyle::Subtle) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |cx| { + Tooltip::for_action_in("New Chat", &NewChat, &focus_handle, cx) + } + }) + .on_click(move |_event, _cx| { + println!("New Chat"); + }), + ) + .child( + IconButton::new("open-history", IconName::HistoryRerun) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| Tooltip::text("Open History", cx)) + .on_click(move |_event, _cx| { + println!("Open History"); + }), + ) + .child( + IconButton::new("configure-assistant", IconName::Settings) + .shape(IconButtonShape::Square) + .icon_size(IconSize::Small) + .style(ButtonStyle::Subtle) + .tooltip(move |cx| Tooltip::text("Configure Assistant", cx)) + .on_click(move |_event, _cx| { + println!("Configure Assistant"); + }), + ), + ) + } + + fn render_language_model_selector(&self, cx: &mut ViewContext) -> impl IntoElement { + let active_provider = LanguageModelRegistry::read_global(cx).active_provider(); + let active_model = LanguageModelRegistry::read_global(cx).active_model(); + + LanguageModelSelector::new( + |model, _cx| { + println!("Selected {:?}", model.name()); + }, + ButtonLike::new("active-model") + .style(ButtonStyle::Subtle) + .child( + h_flex() + .w_full() + .gap_0p5() + .child( + div() + .overflow_x_hidden() + .flex_grow() + .whitespace_nowrap() + .child(match (active_provider, active_model) { + (Some(provider), Some(model)) => h_flex() + .gap_1() + .child( + Icon::new( + model.icon().unwrap_or_else(|| provider.icon()), + ) + .color(Color::Muted) + .size(IconSize::XSmall), + ) + .child( + Label::new(model.name().0) + .size(LabelSize::Small) + .color(Color::Muted), + ) + .into_any_element(), + _ => Label::new("No model selected") + .size(LabelSize::Small) + .color(Color::Muted) + .into_any_element(), + }), + ) + .child( + Icon::new(IconName::ChevronDown) + .color(Color::Muted) + .size(IconSize::XSmall), + ), + ) + .tooltip(move |cx| Tooltip::for_action("Change Model", &ToggleModelSelector, cx)), + ) + } +} + +impl Render for AssistantPanel { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + v_flex() + .key_context("AssistantPanel2") + .size_full() + .on_action(cx.listener(|_this, _: &NewChat, _cx| { + println!("Action: New Chat"); + })) + .child(self.render_toolbar(cx)) } } From 628b96f2972aaeb0ba8b10ecb3b8ee76473db619 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Sat, 23 Nov 2024 14:09:15 -0500 Subject: [PATCH 149/157] assistant2: Sketch in chat editor (#21116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR sketches in the chat editor for `assistant2`. Screenshot 2024-11-23 at 1 56 23 PM Release Notes: - N/A --- Cargo.lock | 3 + crates/assistant2/Cargo.toml | 3 + crates/assistant2/src/assistant.rs | 1 + crates/assistant2/src/assistant_panel.rs | 15 ++++- crates/assistant2/src/chat_editor.rs | 76 ++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 crates/assistant2/src/chat_editor.rs diff --git a/Cargo.lock b/Cargo.lock index 2a5c4d94bb..91205f214f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -456,11 +456,14 @@ version = "0.1.0" dependencies = [ "anyhow", "command_palette_hooks", + "editor", "feature_flags", "gpui", "language_model", "language_model_selector", "proto", + "settings", + "theme", "ui", "workspace", ] diff --git a/crates/assistant2/Cargo.toml b/crates/assistant2/Cargo.toml index 8e3405c340..9dd605d559 100644 --- a/crates/assistant2/Cargo.toml +++ b/crates/assistant2/Cargo.toml @@ -15,10 +15,13 @@ doctest = false [dependencies] anyhow.workspace = true command_palette_hooks.workspace = true +editor.workspace = true feature_flags.workspace = true gpui.workspace = true language_model.workspace = true language_model_selector.workspace = true proto.workspace = true +settings.workspace = true +theme.workspace = true ui.workspace = true workspace.workspace = true diff --git a/crates/assistant2/src/assistant.rs b/crates/assistant2/src/assistant.rs index da1038f2ca..f8284d9ff5 100644 --- a/crates/assistant2/src/assistant.rs +++ b/crates/assistant2/src/assistant.rs @@ -1,4 +1,5 @@ mod assistant_panel; +mod chat_editor; use command_palette_hooks::CommandPaletteFilter; use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt}; diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs index 51bfb8c4ff..2fa08d7f5e 100644 --- a/crates/assistant2/src/assistant_panel.rs +++ b/crates/assistant2/src/assistant_panel.rs @@ -9,6 +9,7 @@ use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, Tab, Tooltip}; use workspace::dock::{DockPosition, Panel, PanelEvent}; use workspace::{Pane, Workspace}; +use crate::chat_editor::ChatEditor; use crate::{NewChat, ToggleFocus, ToggleModelSelector}; pub fn init(cx: &mut AppContext) { @@ -24,6 +25,7 @@ pub fn init(cx: &mut AppContext) { pub struct AssistantPanel { pane: View, + chat_editor: View, } impl AssistantPanel { @@ -54,7 +56,10 @@ impl AssistantPanel { pane }); - Self { pane } + Self { + pane, + chat_editor: cx.new_view(ChatEditor::new), + } } } @@ -231,10 +236,18 @@ impl Render for AssistantPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_flex() .key_context("AssistantPanel2") + .justify_between() .size_full() .on_action(cx.listener(|_this, _: &NewChat, _cx| { println!("Action: New Chat"); })) .child(self.render_toolbar(cx)) + .child(v_flex().bg(cx.theme().colors().panel_background)) + .child( + h_flex() + .border_t_1() + .border_color(cx.theme().colors().border_variant) + .child(self.chat_editor.clone()), + ) } } diff --git a/crates/assistant2/src/chat_editor.rs b/crates/assistant2/src/chat_editor.rs new file mode 100644 index 0000000000..9111f57eac --- /dev/null +++ b/crates/assistant2/src/chat_editor.rs @@ -0,0 +1,76 @@ +use editor::{Editor, EditorElement, EditorStyle}; +use gpui::{TextStyle, View}; +use settings::Settings; +use theme::ThemeSettings; +use ui::prelude::*; + +pub struct ChatEditor { + editor: View, +} + +impl ChatEditor { + pub fn new(cx: &mut ViewContext) -> Self { + Self { + editor: cx.new_view(|cx| { + let mut editor = Editor::auto_height(80, cx); + editor.set_placeholder_text("Ask anything…", cx); + + editor + }), + } + } +} + +impl Render for ChatEditor { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let font_size = TextSize::Default.rems(cx); + let line_height = font_size.to_pixels(cx.rem_size()) * 1.3; + + v_flex() + .size_full() + .gap_2() + .p_2() + .bg(cx.theme().colors().editor_background) + .child({ + let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { + color: cx.theme().colors().editor_foreground, + font_family: settings.ui_font.family.clone(), + font_features: settings.ui_font.features.clone(), + font_size: font_size.into(), + font_weight: settings.ui_font.weight, + line_height: line_height.into(), + ..Default::default() + }; + + EditorElement::new( + &self.editor, + EditorStyle { + background: cx.theme().colors().editor_background, + local_player: cx.theme().players().local(), + text: text_style, + ..Default::default() + }, + ) + }) + .child( + h_flex() + .justify_between() + .child( + h_flex().child( + Button::new("add-context", "Add Context") + .style(ButtonStyle::Filled) + .icon(IconName::Plus) + .icon_position(IconPosition::Start), + ), + ) + .child( + h_flex() + .gap_2() + .child(Button::new("codebase", "Codebase").style(ButtonStyle::Filled)) + .child(Label::new("or")) + .child(Button::new("chat", "Chat").style(ButtonStyle::Filled)), + ), + ) + } +} From 0395d1b03756dd305eae93b5fa484a9f7734de20 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Sat, 23 Nov 2024 23:11:45 -0500 Subject: [PATCH 150/157] Clean up app event transformations (#21115) This needs scrutinized. Detailed breakdown of what events I kept and threw out here: https://zed.dev/channel/app-events-17178/notes I also removed a few fake events and tossed out json properties that were being inserted for things we don't have logic to track. See PR review comments below. I think the only bad data we have are that we were identifying all node, pnpm, and yarn projects as 'node' in the `project_type` property, so a few days of lost data there. Release Notes: - N/A --- crates/collab/src/api/events.rs | 93 ++++++++++++++++----------------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 95bd2a89b2..68325b17aa 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -1442,65 +1442,64 @@ fn for_snowflake( Event::App(e) => { let mut properties = json!({}); let event_type = match e.operation.trim() { - "extensions: install extension" => "Extension Installed".to_string(), + // App "open" => "App Opened".to_string(), - "project search: open" => "Project Search Opened".to_string(), - "first open" => { - properties["is_first_open"] = json!(true); - "App First Opened".to_string() + "first open" => "App First Opened".to_string(), + "first open for release channel" => { + "App First Opened For Release Channel".to_string() } - "extensions: uninstall extension" => "Extension Uninstalled".to_string(), - "welcome page: close" => "Welcome Page Closed".to_string(), - "open project" => { - properties["is_first_time"] = json!(false); + "close" => "App Closed".to_string(), + + // Project + "open project" => "Project Opened".to_string(), + "open node project" => { + properties["project_type"] = json!("node"); "Project Opened".to_string() } - "welcome page: install cli" => "CLI Installed".to_string(), - "project diagnostics: open" => "Project Diagnostics Opened".to_string(), - "extensions page: open" => "Extensions Page Opened".to_string(), - "welcome page: change theme" => "Welcome Theme Changed".to_string(), - "welcome page: toggle metric telemetry" => { - properties["enabled"] = json!(false); - "Welcome Telemetry Toggled".to_string() + "open pnpm project" => { + properties["project_type"] = json!("pnpm"); + "Project Opened".to_string() } + "open yarn project" => { + properties["project_type"] = json!("yarn"); + "Project Opened".to_string() + } + + // SSH + "create ssh server" => "SSH Server Created".to_string(), + "create ssh project" => "SSH Project Created".to_string(), + "open ssh project" => "SSH Project Opened".to_string(), + + // Welcome Page "welcome page: change keymap" => "Keymap Changed".to_string(), - "welcome page: toggle vim" => { - properties["enabled"] = json!(false); - "Welcome Vim Mode Toggled".to_string() - } + "welcome page: change theme" => "Welcome Theme Changed".to_string(), + "welcome page: close" => "Welcome Page Closed".to_string(), + "welcome page: edit settings" => "Settings Edited".to_string(), + "welcome page: install cli" => "CLI Installed".to_string(), + "welcome page: open" => "Welcome Page Opened".to_string(), + "welcome page: open extensions" => "Extensions Page Opened".to_string(), "welcome page: sign in to copilot" => "Welcome Copilot Signed In".to_string(), "welcome page: toggle diagnostic telemetry" => { "Welcome Telemetry Toggled".to_string() } - "welcome page: open" => "Welcome Page Opened".to_string(), - "close" => "App Closed".to_string(), - "markdown preview: open" => "Markdown Preview Opened".to_string(), - "welcome page: open extensions" => "Extensions Page Opened".to_string(), - "open node project" | "open pnpm project" | "open yarn project" => { - properties["project_type"] = json!("node"); - properties["is_first_time"] = json!(false); - "Project Opened".to_string() - } - "repl sessions: open" => "REPL Session Started".to_string(), - "welcome page: toggle helix" => { - properties["enabled"] = json!(false); - "Helix Mode Toggled".to_string() - } - "welcome page: edit settings" => { - properties["changed_settings"] = json!([]); - "Settings Edited".to_string() + "welcome page: toggle metric telemetry" => { + "Welcome Telemetry Toggled".to_string() } + "welcome page: toggle vim" => "Welcome Vim Mode Toggled".to_string(), "welcome page: view docs" => "Documentation Viewed".to_string(), - "open ssh project" => { - properties["is_first_time"] = json!(false); - "SSH Project Opened".to_string() - } - "create ssh server" => "SSH Server Created".to_string(), - "create ssh project" => "SSH Project Created".to_string(), - "first open for release channel" => { - properties["is_first_for_channel"] = json!(true); - "App First Opened For Release Channel".to_string() - } + + // Extensions + "extensions page: open" => "Extensions Page Opened".to_string(), + "extensions: install extension" => "Extension Installed".to_string(), + "extensions: uninstall extension" => "Extension Uninstalled".to_string(), + + // Misc + "markdown preview: open" => "Markdown Preview Opened".to_string(), + "project diagnostics: open" => "Project Diagnostics Opened".to_string(), + "project search: open" => "Project Search Opened".to_string(), + "repl sessions: open" => "REPL Session Started".to_string(), + + // Feature Upsell "feature upsell: toggle vim" => { properties["source"] = json!("Feature Upsell"); "Vim Mode Toggled".to_string() From 3dcb94c204588c112c5c14a1572295e297c847a7 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Sun, 24 Nov 2024 00:34:02 -0500 Subject: [PATCH 151/157] Correct more app event inconsistencies (#21129) - Unify welcome page event type string structure - Differentiate between metric telemetry event and diagnostic telemetry event Release Notes: - N/A --- crates/collab/src/api/events.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index 68325b17aa..b5cd920fb3 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -1471,22 +1471,22 @@ fn for_snowflake( "open ssh project" => "SSH Project Opened".to_string(), // Welcome Page - "welcome page: change keymap" => "Keymap Changed".to_string(), + "welcome page: change keymap" => "Welcome Keymap Changed".to_string(), "welcome page: change theme" => "Welcome Theme Changed".to_string(), "welcome page: close" => "Welcome Page Closed".to_string(), - "welcome page: edit settings" => "Settings Edited".to_string(), - "welcome page: install cli" => "CLI Installed".to_string(), + "welcome page: edit settings" => "Welcome Settings Edited".to_string(), + "welcome page: install cli" => "Welcome CLI Installed".to_string(), "welcome page: open" => "Welcome Page Opened".to_string(), - "welcome page: open extensions" => "Extensions Page Opened".to_string(), + "welcome page: open extensions" => "Welcome Extensions Page Opened".to_string(), "welcome page: sign in to copilot" => "Welcome Copilot Signed In".to_string(), "welcome page: toggle diagnostic telemetry" => { - "Welcome Telemetry Toggled".to_string() + "Welcome Diagnostic Telemetry Toggled".to_string() } "welcome page: toggle metric telemetry" => { - "Welcome Telemetry Toggled".to_string() + "Welcome Metric Telemetry Toggled".to_string() } "welcome page: toggle vim" => "Welcome Vim Mode Toggled".to_string(), - "welcome page: view docs" => "Documentation Viewed".to_string(), + "welcome page: view docs" => "Welcome Documentation Viewed".to_string(), // Extensions "extensions page: open" => "Extensions Page Opened".to_string(), From 20bffaf93f3598d129ab654493bb866af33a6152 Mon Sep 17 00:00:00 2001 From: Carroll Wainwright Date: Sun, 24 Nov 2024 15:52:11 -0800 Subject: [PATCH 152/157] python: Highlight docstrings for classes and modules (#20486) Release Notes: - Add `string.doc` python syntax highlighting to class and module-level docstrings. Previously, only docstrings inside python functions were labeled as `string.doc`, but docstrings can exist at the class or module level too. This adds the more specific string type for each of those. *Before*: image *After*: image --- crates/languages/src/python/highlights.scm | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/languages/src/python/highlights.scm b/crates/languages/src/python/highlights.scm index 6c3f027c19..98ed203969 100644 --- a/crates/languages/src/python/highlights.scm +++ b/crates/languages/src/python/highlights.scm @@ -96,7 +96,16 @@ "def" name: (_) (parameters)? - body: (block (expression_statement (string) @string.doc))) + body: (block . (expression_statement (string) @string.doc))) + +(class_definition + body: (block + . (comment) @comment* + . (expression_statement (string) @string.doc))) + +(module + . (comment) @comment* + . (expression_statement (string) @string.doc)) (module (expression_statement (assignment)) From e85848a69508d901fc33f8d8883e1f7c356fb8a2 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 25 Nov 2024 00:54:47 +0100 Subject: [PATCH 153/157] pylsp: Prefer version from user venv (#21069) Closes #ISSUE Release Notes: - pylsp will now use version installed in user venv, if one is available. --- crates/language/src/language.rs | 9 ++++- .../src/extension_lsp_adapter.rs | 1 + crates/languages/src/c.rs | 1 + crates/languages/src/go.rs | 1 + crates/languages/src/python.rs | 37 +++++++++---------- crates/languages/src/rust.rs | 1 + crates/languages/src/vtsls.rs | 1 + crates/project/src/lsp_store.rs | 9 ++++- 8 files changed, 37 insertions(+), 23 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 580955a98b..58be8a4dc3 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -201,13 +201,14 @@ impl CachedLspAdapter { pub async fn get_language_server_command( self: Arc, delegate: Arc, + toolchains: Arc, binary_options: LanguageServerBinaryOptions, cx: &mut AsyncAppContext, ) -> Result { let cached_binary = self.cached_binary.lock().await; self.adapter .clone() - .get_language_server_command(delegate, binary_options, cached_binary, cx) + .get_language_server_command(delegate, toolchains, binary_options, cached_binary, cx) .await } @@ -281,6 +282,7 @@ pub trait LspAdapter: 'static + Send + Sync { fn get_language_server_command<'a>( self: Arc, delegate: Arc, + toolchains: Arc, binary_options: LanguageServerBinaryOptions, mut cached_binary: futures::lock::MutexGuard<'a, Option>, cx: &'a mut AsyncAppContext, @@ -298,7 +300,7 @@ pub trait LspAdapter: 'static + Send + Sync { // because we don't want to download and overwrite our global one // for each worktree we might have open. if binary_options.allow_path_lookup { - if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), cx).await { + if let Some(binary) = self.check_if_user_installed(delegate.as_ref(), toolchains, cx).await { log::info!( "found user-installed language server for {}. path: {:?}, arguments: {:?}", self.name().0, @@ -357,6 +359,7 @@ pub trait LspAdapter: 'static + Send + Sync { async fn check_if_user_installed( &self, _: &dyn LspAdapterDelegate, + _: Arc, _: &AsyncAppContext, ) -> Option { None @@ -1665,6 +1668,7 @@ impl LspAdapter for FakeLspAdapter { async fn check_if_user_installed( &self, _: &dyn LspAdapterDelegate, + _: Arc, _: &AsyncAppContext, ) -> Option { Some(self.language_server_binary.clone()) @@ -1673,6 +1677,7 @@ impl LspAdapter for FakeLspAdapter { fn get_language_server_command<'a>( self: Arc, _: Arc, + _: Arc, _: LanguageServerBinaryOptions, _: futures::lock::MutexGuard<'a, Option>, _: &'a mut AsyncAppContext, diff --git a/crates/language_extension/src/extension_lsp_adapter.rs b/crates/language_extension/src/extension_lsp_adapter.rs index eab9529fe0..3286e09e2d 100644 --- a/crates/language_extension/src/extension_lsp_adapter.rs +++ b/crates/language_extension/src/extension_lsp_adapter.rs @@ -115,6 +115,7 @@ impl LspAdapter for ExtensionLspAdapter { fn get_language_server_command<'a>( self: Arc, delegate: Arc, + _: Arc, _: LanguageServerBinaryOptions, _: futures::lock::MutexGuard<'a, Option>, _: &'a mut AsyncAppContext, diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index 5bfb7f0bc2..8d0369f0e0 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -24,6 +24,7 @@ impl super::LspAdapter for CLspAdapter { async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, + _: Arc, _: &AsyncAppContext, ) -> Option { let path = delegate.which(Self::SERVER_NAME.as_ref()).await?; diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index b3073d7eaa..6e2b5d464e 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -67,6 +67,7 @@ impl super::LspAdapter for GoLspAdapter { async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, + _: Arc, _: &AsyncAppContext, ) -> Option { let path = delegate.which(Self::SERVER_NAME.as_ref()).await?; diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 2cedd704cf..8736a12942 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -79,6 +79,7 @@ impl LspAdapter for PythonLspAdapter { async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, + _: Arc, _: &AsyncAppContext, ) -> Option { let node = delegate.which("node".as_ref()).await?; @@ -753,33 +754,29 @@ impl LspAdapter for PyLspAdapter { async fn check_if_user_installed( &self, - _: &dyn LspAdapterDelegate, - _: &AsyncAppContext, + delegate: &dyn LspAdapterDelegate, + toolchains: Arc, + cx: &AsyncAppContext, ) -> Option { - // We don't support user-provided pylsp, as global packages are discouraged in Python ecosystem. - None + let venv = toolchains + .active_toolchain( + delegate.worktree_id(), + LanguageName::new("Python"), + &mut cx.clone(), + ) + .await?; + let pylsp_path = Path::new(venv.path.as_ref()).parent()?.join("pylsp"); + pylsp_path.exists().then(|| LanguageServerBinary { + path: venv.path.to_string().into(), + arguments: vec![pylsp_path.into()], + env: None, + }) } async fn fetch_latest_server_version( &self, _: &dyn LspAdapterDelegate, ) -> Result> { - // let uri = "https://pypi.org/pypi/python-lsp-server/json"; - // let mut root_manifest = delegate - // .http_client() - // .get(&uri, Default::default(), true) - // .await?; - // let mut body = Vec::new(); - // root_manifest.body_mut().read_to_end(&mut body).await?; - // let as_str = String::from_utf8(body)?; - // let json = serde_json::Value::from_str(&as_str)?; - // let latest_version = json - // .get("info") - // .and_then(|info| info.get("version")) - // .and_then(|version| version.as_str().map(ToOwned::to_owned)) - // .ok_or_else(|| { - // anyhow!("PyPI response did not contain version info for python-language-server") - // })?; Ok(Box::new(()) as Box<_>) } diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index 7f5912d73e..25cddae5a6 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -76,6 +76,7 @@ impl LspAdapter for RustLspAdapter { async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, + _: Arc, _: &AsyncAppContext, ) -> Option { let path = delegate.which("rust-analyzer".as_ref()).await?; diff --git a/crates/languages/src/vtsls.rs b/crates/languages/src/vtsls.rs index 0ad9158003..e44e4e295f 100644 --- a/crates/languages/src/vtsls.rs +++ b/crates/languages/src/vtsls.rs @@ -77,6 +77,7 @@ impl LspAdapter for VtslsLspAdapter { async fn check_if_user_installed( &self, delegate: &dyn LspAdapterDelegate, + _: Arc, _: &AsyncAppContext, ) -> Option { let env = delegate.shell_env().await; diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 29a4c8e71b..cc326285cb 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -5523,10 +5523,16 @@ impl LspStore { .unwrap_or_default(), allow_binary_download, }; + let toolchains = self.toolchain_store(cx); cx.spawn(|_, mut cx| async move { let binary_result = adapter .clone() - .get_language_server_command(delegate.clone(), lsp_binary_options, &mut cx) + .get_language_server_command( + delegate.clone(), + toolchains, + lsp_binary_options, + &mut cx, + ) .await; delegate.update_status(adapter.name.clone(), LanguageServerBinaryStatus::None); @@ -7783,6 +7789,7 @@ impl LspAdapter for SshLspAdapter { async fn check_if_user_installed( &self, _: &dyn LspAdapterDelegate, + _: Arc, _: &AsyncAppContext, ) -> Option { Some(self.binary.clone()) From 5b0fa6e585dd0b3bb4d81813051b1b8931d24371 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Nov 2024 01:48:43 -0700 Subject: [PATCH 154/157] Hide AI hints on line ends so we can discuss more (#21128) @bennetbo @as-cii @mrnugget I'm really not liking the hints about AI on every line. It feels too distracting to me and damaging to the user experience. I'm wondering if we can hide them and work with design for other ideas. Or at least talk it through. Release Notes: - N/A --- assets/settings/default.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 45a211789f..f1071f9676 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -492,7 +492,7 @@ "enabled": true, // Whether to show inline hints showing the keybindings to use the inline assistant and the // assistant panel. - "show_hints": true, + "show_hints": false, // Whether to show the assistant panel button in the status bar. "button": true, // Where to dock the assistant panel. Can be 'left', 'right' or 'bottom'. From aa58cab766a64f79ca8c25ae01b9b9c17ad2d4f0 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:21:32 +0100 Subject: [PATCH 155/157] Fix offline workspace deserialization with assistant2 (#21159) Closes #21156 /cc @maxdeviant Release Notes: - N/A --- crates/zed/src/zed.rs | 53 ++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 086935542c..be6df49a2d 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -241,25 +241,6 @@ pub fn initialize_workspace( let prompt_builder = prompt_builder.clone(); cx.spawn(|workspace_handle, mut cx| async move { - let is_assistant2_enabled = if cfg!(test) { - false - } else { - let is_assistant2_feature_flag_enabled = assistant2_feature_flag.await; - release_channel == ReleaseChannel::Dev && is_assistant2_feature_flag_enabled - }; - - let (assistant_panel, assistant2_panel) = if is_assistant2_enabled { - let assistant2_panel = - assistant2::AssistantPanel::load(workspace_handle.clone(), cx.clone()).await?; - - (None, Some(assistant2_panel)) - } else { - let assistant_panel = - assistant::AssistantPanel::load(workspace_handle.clone(), prompt_builder, cx.clone()).await?; - - (Some(assistant_panel), None) - }; - let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone()); let outline_panel = OutlinePanel::load(workspace_handle.clone(), cx.clone()); let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone()); @@ -288,6 +269,33 @@ pub fn initialize_workspace( notification_panel, )?; + workspace_handle.update(&mut cx, |workspace, cx| { + workspace.add_panel(project_panel, cx); + workspace.add_panel(outline_panel, cx); + workspace.add_panel(terminal_panel, cx); + workspace.add_panel(channels_panel, cx); + workspace.add_panel(chat_panel, cx); + workspace.add_panel(notification_panel, cx); + })?; + let is_assistant2_enabled = + if cfg!(test) || release_channel != ReleaseChannel::Dev { + false + } else { + assistant2_feature_flag.await + } + ; + + let (assistant_panel, assistant2_panel) = if is_assistant2_enabled { + let assistant2_panel = + assistant2::AssistantPanel::load(workspace_handle.clone(), cx.clone()).await?; + + (None, Some(assistant2_panel)) + } else { + let assistant_panel = + assistant::AssistantPanel::load(workspace_handle.clone(), prompt_builder, cx.clone()).await?; + + (Some(assistant_panel), None) + }; workspace_handle.update(&mut cx, |workspace, cx| { if let Some(assistant_panel) = assistant_panel { workspace.add_panel(assistant_panel, cx); @@ -296,13 +304,6 @@ pub fn initialize_workspace( if let Some(assistant2_panel) = assistant2_panel { workspace.add_panel(assistant2_panel, cx); } - - workspace.add_panel(project_panel, cx); - workspace.add_panel(outline_panel, cx); - workspace.add_panel(terminal_panel, cx); - workspace.add_panel(channels_panel, cx); - workspace.add_panel(chat_panel, cx); - workspace.add_panel(notification_panel, cx); }) }) .detach(); From 08b214dfb9b9f59e2e1fdff6d2112dde33ca61aa Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 25 Nov 2024 05:27:35 -0700 Subject: [PATCH 156/157] Rename 'chat' to 'thread' in assistant2 (#21141) Release Notes: - N/A --- crates/assistant2/src/assistant.rs | 4 +-- crates/assistant2/src/assistant_panel.rs | 29 +++++++++++-------- .../src/{chat_editor.rs => message_editor.rs} | 6 ++-- 3 files changed, 22 insertions(+), 17 deletions(-) rename crates/assistant2/src/{chat_editor.rs => message_editor.rs} (97%) diff --git a/crates/assistant2/src/assistant.rs b/crates/assistant2/src/assistant.rs index f8284d9ff5..6a80186525 100644 --- a/crates/assistant2/src/assistant.rs +++ b/crates/assistant2/src/assistant.rs @@ -1,5 +1,5 @@ mod assistant_panel; -mod chat_editor; +mod message_editor; use command_palette_hooks::CommandPaletteFilter; use feature_flags::{Assistant2FeatureFlag, FeatureFlagAppExt}; @@ -7,7 +7,7 @@ use gpui::{actions, AppContext}; pub use crate::assistant_panel::AssistantPanel; -actions!(assistant2, [ToggleFocus, NewChat, ToggleModelSelector]); +actions!(assistant2, [ToggleFocus, NewThread, ToggleModelSelector]); const NAMESPACE: &str = "assistant2"; diff --git a/crates/assistant2/src/assistant_panel.rs b/crates/assistant2/src/assistant_panel.rs index 2fa08d7f5e..890020e54a 100644 --- a/crates/assistant2/src/assistant_panel.rs +++ b/crates/assistant2/src/assistant_panel.rs @@ -9,8 +9,8 @@ use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, Tab, Tooltip}; use workspace::dock::{DockPosition, Panel, PanelEvent}; use workspace::{Pane, Workspace}; -use crate::chat_editor::ChatEditor; -use crate::{NewChat, ToggleFocus, ToggleModelSelector}; +use crate::message_editor::MessageEditor; +use crate::{NewThread, ToggleFocus, ToggleModelSelector}; pub fn init(cx: &mut AppContext) { cx.observe_new_views( @@ -25,7 +25,7 @@ pub fn init(cx: &mut AppContext) { pub struct AssistantPanel { pane: View, - chat_editor: View, + message_editor: View, } impl AssistantPanel { @@ -47,7 +47,7 @@ impl AssistantPanel { workspace.project().clone(), Default::default(), None, - NewChat.boxed_clone(), + NewThread.boxed_clone(), cx, ); pane.set_can_split(false, cx); @@ -58,7 +58,7 @@ impl AssistantPanel { Self { pane, - chat_editor: cx.new_view(ChatEditor::new), + message_editor: cx.new_view(MessageEditor::new), } } } @@ -136,25 +136,30 @@ impl AssistantPanel { .bg(cx.theme().colors().tab_bar_background) .border_b_1() .border_color(cx.theme().colors().border_variant) - .child(h_flex().child(Label::new("Chat Title Goes Here"))) + .child(h_flex().child(Label::new("Thread Title Goes Here"))) .child( h_flex() .gap(DynamicSpacing::Base08.rems(cx)) .child(self.render_language_model_selector(cx)) .child(Divider::vertical()) .child( - IconButton::new("new-chat", IconName::Plus) + IconButton::new("new-thread", IconName::Plus) .shape(IconButtonShape::Square) .icon_size(IconSize::Small) .style(ButtonStyle::Subtle) .tooltip({ let focus_handle = focus_handle.clone(); move |cx| { - Tooltip::for_action_in("New Chat", &NewChat, &focus_handle, cx) + Tooltip::for_action_in( + "New Thread", + &NewThread, + &focus_handle, + cx, + ) } }) .on_click(move |_event, _cx| { - println!("New Chat"); + println!("New Thread"); }), ) .child( @@ -238,8 +243,8 @@ impl Render for AssistantPanel { .key_context("AssistantPanel2") .justify_between() .size_full() - .on_action(cx.listener(|_this, _: &NewChat, _cx| { - println!("Action: New Chat"); + .on_action(cx.listener(|_this, _: &NewThread, _cx| { + println!("Action: New Thread"); })) .child(self.render_toolbar(cx)) .child(v_flex().bg(cx.theme().colors().panel_background)) @@ -247,7 +252,7 @@ impl Render for AssistantPanel { h_flex() .border_t_1() .border_color(cx.theme().colors().border_variant) - .child(self.chat_editor.clone()), + .child(self.message_editor.clone()), ) } } diff --git a/crates/assistant2/src/chat_editor.rs b/crates/assistant2/src/message_editor.rs similarity index 97% rename from crates/assistant2/src/chat_editor.rs rename to crates/assistant2/src/message_editor.rs index 9111f57eac..ee25ad5da7 100644 --- a/crates/assistant2/src/chat_editor.rs +++ b/crates/assistant2/src/message_editor.rs @@ -4,11 +4,11 @@ use settings::Settings; use theme::ThemeSettings; use ui::prelude::*; -pub struct ChatEditor { +pub struct MessageEditor { editor: View, } -impl ChatEditor { +impl MessageEditor { pub fn new(cx: &mut ViewContext) -> Self { Self { editor: cx.new_view(|cx| { @@ -21,7 +21,7 @@ impl ChatEditor { } } -impl Render for ChatEditor { +impl Render for MessageEditor { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let font_size = TextSize::Default.rems(cx); let line_height = font_size.to_pixels(cx.rem_size()) * 1.3; From b83f104f6eea872e18ea2599497328ed26a5d8b4 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 25 Nov 2024 15:58:45 +0200 Subject: [PATCH 157/157] Do not reuse render cache for nested items whose parents are re-rendered (#21165) Fixes a bug with terminal splits panicking during writing a command in the command input Release Notes: - N/A Co-authored-by: Antonio Scandurra --- crates/gpui/src/view.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 7f10eb25c3..4f35413a27 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -7,6 +7,7 @@ use crate::{ }; use anyhow::{Context, Result}; use refineable::Refineable; +use std::mem; use std::{ any::{type_name, TypeId}, fmt, @@ -341,11 +342,13 @@ impl Element for AnyView { } } + let refreshing = mem::replace(&mut cx.window.refreshing, true); let prepaint_start = cx.prepaint_index(); let mut element = (self.render)(self, cx); element.layout_as_root(bounds.size.into(), cx); element.prepaint_at(bounds.origin, cx); let prepaint_end = cx.prepaint_index(); + cx.window.refreshing = refreshing; ( Some(element), @@ -382,7 +385,9 @@ impl Element for AnyView { let paint_start = cx.paint_index(); if let Some(element) = element { + let refreshing = mem::replace(&mut cx.window.refreshing, true); element.paint(cx); + cx.window.refreshing = refreshing; } else { cx.reuse_paint(element_state.paint_range.clone()); }