diff --git a/Cargo.lock b/Cargo.lock index 9a4986fa1d9bc2beec2decca4e05479842d7a978..9dd3c00221b171cbe821d38e966f1d54f55d9f99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,22 +111,13 @@ dependencies = [ "workspace", ] -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli 0.31.1", -] - [[package]] name = "addr2line" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ - "gimli 0.32.3", + "gimli", ] [[package]] @@ -674,7 +665,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" dependencies = [ - "object 0.37.3", + "object", ] [[package]] @@ -1821,11 +1812,11 @@ version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ - "addr2line 0.25.1", + "addr2line", "cfg-if", "libc", "miniz_oxide", - "object 0.37.3", + "object", "rustc-demangle", "windows-link 0.2.1", ] @@ -3818,36 +3809,36 @@ dependencies = [ [[package]] name = "cranelift-assembler-x64" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5023e06632d8f351c2891793ccccfe4aef957954904392434038745fb6f1f68" +checksum = "ba33ddc4e157cb1abe9da6c821e8824f99e56d057c2c22536850e0141f281d61" dependencies = [ "cranelift-assembler-x64-meta", ] [[package]] name = "cranelift-assembler-x64-meta" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c4012b4c8c1f6eb05c0a0a540e3e1ee992631af51aa2bbb3e712903ce4fd65" +checksum = "69b23dd6ea360e6fb28a3f3b40b7f126509668f58076a4729b2cfd656f26a0ad" dependencies = [ "cranelift-srcgen", ] [[package]] name = "cranelift-bforest" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6d883b4942ef3a7104096b8bc6f2d1a41393f159ac8de12aed27b25d67f895" +checksum = "a9d81afcee8fe27ee2536987df3fadcb2e161af4edb7dbe3ef36838d0ce74382" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-bitset" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7b2ee9eec6ca8a716d900d5264d678fb2c290c58c46c8da7f94ee268175d17" +checksum = "fb33595f1279fe7af03b28245060e9085caf98b10ed3137461a85796eb83972a" dependencies = [ "serde", "serde_derive", @@ -3855,9 +3846,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeda0892577afdce1ac2e9a983a55f8c5b87a59334e1f79d8f735a2d7ba4f4b4" +checksum = "0230a6ac0660bfe31eb244cbb43dcd4f2b3c1c4e0addc3e0348c6053ea60272e" dependencies = [ "bumpalo", "cranelift-assembler-x64", @@ -3868,7 +3859,7 @@ dependencies = [ "cranelift-control", "cranelift-entity", "cranelift-isle", - "gimli 0.31.1", + "gimli", "hashbrown 0.15.5", "log", "postcard", @@ -3880,40 +3871,42 @@ dependencies = [ "sha2", "smallvec", "target-lexicon 0.13.3", + "wasmtime-internal-math", ] [[package]] name = "cranelift-codegen-meta" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e461480d87f920c2787422463313326f67664e68108c14788ba1676f5edfcd15" +checksum = "96d6817fdc15cb8f236fc9d8e610767d3a03327ceca4abff7a14d8e2154c405e" dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", "cranelift-srcgen", + "heck 0.5.0", "pulley-interpreter", ] [[package]] name = "cranelift-codegen-shared" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976584d09f200c6c84c4b9ff7af64fc9ad0cb64dffa5780991edd3fe143a30a1" +checksum = "0403796328e9e2e7df2b80191cdbb473fd9ea3889eb45ef5632d0fef168ea032" [[package]] name = "cranelift-control" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46d43d70f4e17c545aa88dbf4c84d4200755d27c6e3272ebe4de65802fa6a955" +checksum = "188f04092279a3814e0b6235c2f9c2e34028e4beb72da7bfed55cbd184702bcc" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75418674520cb400c8772bfd6e11a62736c78fc1b6e418195696841d1bf91f1" +checksum = "43f5e7391167605d505fe66a337e1a69583b3f34b63d359ffa5a430313c555e8" dependencies = [ "cranelift-bitset", "serde", @@ -3922,9 +3915,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8b1a91c86687a344f3c52dd6dfb6e50db0dfa7f2e9c7711b060b3623e1fdeb" +checksum = "ea5440792eb2b5ba0a0976df371b9f94031bd853ae56f389de610bca7128a7cb" dependencies = [ "cranelift-codegen", "log", @@ -3934,15 +3927,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711baa4e3432d4129295b39ec2b4040cc1b558874ba0a37d08e832e857db7285" +checksum = "1e5c05fab6fce38d729088f3fa1060eaa1ad54eefd473588887205ed2ab2f79e" [[package]] name = "cranelift-native" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c83e8666e3bcc5ffeaf6f01f356f0e1f9dcd69ce5511a1efd7ca5722001a3f" +checksum = "9c9a0607a028edf5ba5bba7e7cf5ca1b7f0a030e3ae84dcd401e8b9b05192280" dependencies = [ "cranelift-codegen", "libc", @@ -3951,9 +3944,9 @@ dependencies = [ [[package]] name = "cranelift-srcgen" -version = "0.120.2" +version = "0.123.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e3f4d783a55c64266d17dc67d2708852235732a100fc40dd9f1051adc64d7b" +checksum = "cb0f2da72eb2472aaac6cfba4e785af42b1f2d82f5155f30c9c30e8cce351e17" [[package]] name = "crash-context" @@ -7054,21 +7047,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" dependencies = [ "fallible-iterator", "indexmap", "stable_deref_trait", ] -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - [[package]] name = "gio-sys" version = "0.21.5" @@ -7614,6 +7601,7 @@ dependencies = [ "media", "metal", "objc", + "objc2-app-kit", "parking_lot", "pathfinder_geometry", "raw-window-handle", @@ -11224,6 +11212,16 @@ dependencies = [ "objc2-encode", ] +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "objc2-audio-toolbox" version = "0.3.2" @@ -11361,9 +11359,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "crc32fast", "hashbrown 0.15.5", @@ -11371,15 +11369,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - [[package]] name = "ollama" version = "0.1.0" @@ -13518,13 +13507,25 @@ checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3" [[package]] name = "pulley-interpreter" -version = "33.0.2" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "986beaef947a51d17b42b0ea18ceaa88450d35b6994737065ed505c39172db71" +checksum = "499d922aa0f9faac8d92351416664f1b7acd914008a90fce2f0516d31efddf67" dependencies = [ "cranelift-bitset", "log", - "wasmtime-math", + "pulley-macros", + "wasmtime-internal-math", +] + +[[package]] +name = "pulley-macros" +version = "36.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3848fb193d6dffca43a21f24ca9492f22aab88af1223d06bac7f8a0ef405b81" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -16195,12 +16196,6 @@ dependencies = [ "der 0.7.10", ] -[[package]] -name = "sptr" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" - [[package]] name = "sqlez" version = "0.1.0" @@ -18243,17 +18238,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "trait-variant" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "transpose" version = "0.2.3" @@ -18266,9 +18250,9 @@ dependencies = [ [[package]] name = "tree-sitter" -version = "0.26.3" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "974d205cc395652cfa8b37daa053fe56eebd429acf8dc055503fee648dae981e" +checksum = "887bd495d0582c5e3e0d8ece2233666169fa56a9644d172fc22ad179ab2d0538" dependencies = [ "cc", "regex", @@ -19361,12 +19345,12 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.229.0" +version = "0.236.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ba1d491ecacb085a2552025c10a675a6fddcbd03b1fc9b36c536010ce265d2" +checksum = "724fccfd4f3c24b7e589d333fc0429c68042897a7e8a5f8694f31792471841e7" dependencies = [ "leb128fmt", - "wasmparser 0.229.0", + "wasmparser 0.236.1", ] [[package]] @@ -19489,9 +19473,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.229.0" +version = "0.236.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc3b1f053f5d41aa55640a1fa9b6d1b8a9e4418d118ce308d20e24ff3575a8c" +checksum = "a9b1e81f3eb254cf7404a82cee6926a4a3ccc5aad80cc3d43608a070c67aa1d7" dependencies = [ "bitflags 2.10.0", "hashbrown 0.15.5", @@ -19514,22 +19498,22 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.229.0" +version = "0.236.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25dac01892684a99b8fbfaf670eb6b56edea8a096438c75392daeb83156ae2e" +checksum = "2df225df06a6df15b46e3f73ca066ff92c2e023670969f7d50ce7d5e695abbb1" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.229.0", + "wasmparser 0.236.1", ] [[package]] name = "wasmtime" -version = "33.0.2" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57373e1d8699662fb791270ac5dfac9da5c14f618ecf940cdb29dc3ad9472a3c" +checksum = "6a2f8736ddc86e03a9d0e4c477a37939cfc53cd1b052ee38a3133679b87ef830" dependencies = [ - "addr2line 0.24.2", + "addr2line", "anyhow", "async-trait", "bitflags 2.10.0", @@ -19543,10 +19527,9 @@ dependencies = [ "log", "mach2 0.4.3", "memfd", - "object 0.36.7", + "object", "once_cell", "postcard", - "psm", "pulley-interpreter", "rayon", "rustix 1.1.2", @@ -19554,82 +19537,109 @@ dependencies = [ "serde", "serde_derive", "smallvec", - "sptr", "target-lexicon 0.13.3", - "trait-variant", - "wasmparser 0.229.0", - "wasmtime-asm-macros", - "wasmtime-component-macro", - "wasmtime-component-util", - "wasmtime-cranelift", + "wasmparser 0.236.1", "wasmtime-environ", - "wasmtime-fiber", - "wasmtime-jit-icache-coherence", - "wasmtime-math", - "wasmtime-slab", - "wasmtime-versioned-export-macros", - "wasmtime-winch", - "windows-sys 0.59.0", + "wasmtime-internal-asm-macros", + "wasmtime-internal-component-macro", + "wasmtime-internal-component-util", + "wasmtime-internal-cranelift", + "wasmtime-internal-fiber", + "wasmtime-internal-jit-debug", + "wasmtime-internal-jit-icache-coherence", + "wasmtime-internal-math", + "wasmtime-internal-slab", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", + "wasmtime-internal-winch", + "windows-sys 0.60.2", ] [[package]] -name = "wasmtime-asm-macros" -version = "33.0.2" +name = "wasmtime-c-api-impl" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0fc91372865167a695dc98d0d6771799a388a7541d3f34e939d0539d6583de" +checksum = "f3c62ea3fa30e6b0cf61116b3035121b8f515c60ac118ebfdab2ee56d028ed1e" dependencies = [ - "cfg-if", + "anyhow", + "log", + "tracing", + "wasmtime", + "wasmtime-internal-c-api-macros", ] [[package]] -name = "wasmtime-c-api-impl" -version = "33.0.2" +name = "wasmtime-environ" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46db556f1dccdd88e0672bd407162ab0036b72e5eccb0f4398d8251cba32dba1" +checksum = "733682a327755c77153ac7455b1ba8f2db4d9946c1738f8002fe1fbda1d52e83" dependencies = [ "anyhow", + "cpp_demangle", + "cranelift-bitset", + "cranelift-entity", + "gimli", + "indexmap", "log", - "tracing", - "wasmtime", - "wasmtime-c-api-macros", + "object", + "postcard", + "rustc-demangle", + "semver", + "serde", + "serde_derive", + "smallvec", + "target-lexicon 0.13.3", + "wasm-encoder 0.236.1", + "wasmparser 0.236.1", + "wasmprinter", + "wasmtime-internal-component-util", ] [[package]] -name = "wasmtime-c-api-macros" -version = "33.0.2" +name = "wasmtime-internal-asm-macros" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "315cc6bc8cdc66f296accb26d7625ae64c1c7b6da6f189e8a72ce6594bf7bd36" +checksum = "68288980a2e02bcb368d436da32565897033ea21918007e3f2bae18843326cf9" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "wasmtime-internal-c-api-macros" +version = "36.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8c61294155a6d23c202f08cf7a2f9392a866edd50517508208818be626ce9f" dependencies = [ "proc-macro2", "quote", ] [[package]] -name = "wasmtime-component-macro" -version = "33.0.2" +name = "wasmtime-internal-component-macro" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25c9c7526675ff9a9794b115023c4af5128e3eb21389bfc3dc1fd344d549258f" +checksum = "5dea846da68f8e776c8a43bde3386022d7bb74e713b9654f7c0196e5ff2e4684" dependencies = [ "anyhow", "proc-macro2", "quote", "syn 2.0.117", - "wasmtime-component-util", - "wasmtime-wit-bindgen", - "wit-parser 0.229.0", + "wasmtime-internal-component-util", + "wasmtime-internal-wit-bindgen", + "wit-parser 0.236.1", ] [[package]] -name = "wasmtime-component-util" -version = "33.0.2" +name = "wasmtime-internal-component-util" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc42ec8b078875804908d797cb4950fec781d9add9684c9026487fd8eb3f6291" +checksum = "fe1e5735b3c8251510d2a55311562772d6c6fca9438a3d0329eb6e38af4957d6" [[package]] -name = "wasmtime-cranelift" -version = "33.0.2" +name = "wasmtime-internal-cranelift" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2bd72f0a6a0ffcc6a184ec86ac35c174e48ea0e97bbae277c8f15f8bf77a566" +checksum = "e89bb9ef571288e2be6b8a3c4763acc56c348dcd517500b1679d3ffad9e4a757" dependencies = [ "anyhow", "cfg-if", @@ -19638,104 +19648,132 @@ dependencies = [ "cranelift-entity", "cranelift-frontend", "cranelift-native", - "gimli 0.31.1", + "gimli", "itertools 0.14.0", "log", - "object 0.36.7", + "object", "pulley-interpreter", "smallvec", "target-lexicon 0.13.3", "thiserror 2.0.17", - "wasmparser 0.229.0", + "wasmparser 0.236.1", "wasmtime-environ", - "wasmtime-versioned-export-macros", + "wasmtime-internal-math", + "wasmtime-internal-versioned-export-macros", ] [[package]] -name = "wasmtime-environ" -version = "33.0.2" +name = "wasmtime-internal-fiber" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6187bb108a23eb25d2a92aa65d6c89fb5ed53433a319038a2558567f3011ff2" +checksum = "b698d004b15ea1f1ae2d06e5e8b80080cbd684fd245220ce2fac3cdd5ecf87f2" dependencies = [ "anyhow", - "cpp_demangle", - "cranelift-bitset", - "cranelift-entity", - "gimli 0.31.1", - "indexmap", - "log", - "object 0.36.7", - "postcard", - "rustc-demangle", - "semver", - "serde", - "serde_derive", - "smallvec", - "target-lexicon 0.13.3", - "wasm-encoder 0.229.0", - "wasmparser 0.229.0", - "wasmprinter", - "wasmtime-component-util", + "cc", + "cfg-if", + "libc", + "rustix 1.1.2", + "wasmtime-internal-asm-macros", + "wasmtime-internal-versioned-export-macros", + "windows-sys 0.60.2", ] [[package]] -name = "wasmtime-fiber" -version = "33.0.2" +name = "wasmtime-internal-jit-debug" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc8965d2128c012329f390e24b8b2758dd93d01bf67e1a1a0dd3d8fd72f56873" +checksum = "c803a9fec05c3d7fa03474d4595079d546e77a3c71c1d09b21f74152e2165c17" dependencies = [ - "anyhow", "cc", - "cfg-if", - "rustix 1.1.2", - "wasmtime-asm-macros", - "wasmtime-versioned-export-macros", - "windows-sys 0.59.0", + "wasmtime-internal-versioned-export-macros", ] [[package]] -name = "wasmtime-jit-icache-coherence" -version = "33.0.2" +name = "wasmtime-internal-jit-icache-coherence" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7af0e940cb062a45c0b3f01a926f77da5947149e99beb4e3dd9846d5b8f11619" +checksum = "d3866909d37f7929d902e6011847748147e8734e9d7e0353e78fb8b98f586aee" dependencies = [ "anyhow", "cfg-if", "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] -name = "wasmtime-math" -version = "33.0.2" +name = "wasmtime-internal-math" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acfca360e719dda9a27e26944f2754ff2fd5bad88e21919c42c5a5f38ddd93cb" +checksum = "5a23b03fb14c64bd0dfcaa4653101f94ade76c34a3027ed2d6b373267536e45b" dependencies = [ "libm", ] [[package]] -name = "wasmtime-slab" -version = "33.0.2" +name = "wasmtime-internal-slab" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e240559cada55c4b24af979d5f6c95e0029f5772f32027ec3c62b258aaff65" +checksum = "fbff220b88cdb990d34a20b13344e5da2e7b99959a5b1666106bec94b58d6364" [[package]] -name = "wasmtime-versioned-export-macros" -version = "33.0.2" +name = "wasmtime-internal-unwinder" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0963c1438357a3d8c0efe152b4ef5259846c1cf8b864340270744fe5b3bae5e" +checksum = "13e1ad30e88988b20c0d1c56ea4b4fbc01a8c614653cbf12ca50c0dcc695e2f7" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "log", + "object", +] + +[[package]] +name = "wasmtime-internal-versioned-export-macros" +version = "36.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549aefdaa1398c2fcfbf69a7b882956bb5b6e8e5b600844ecb91a3b5bf658ca7" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] +[[package]] +name = "wasmtime-internal-winch" +version = "36.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc96a84c5700171aeecf96fa9a9ab234f333f5afb295dabf3f8a812b70fe832" +dependencies = [ + "anyhow", + "cranelift-codegen", + "gimli", + "object", + "target-lexicon 0.13.3", + "wasmparser 0.236.1", + "wasmtime-environ", + "wasmtime-internal-cranelift", + "winch-codegen", +] + +[[package]] +name = "wasmtime-internal-wit-bindgen" +version = "36.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28dc9efea511598c88564ac1974e0825c07d9c0de902dbf68f227431cd4ff8c" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "heck 0.5.0", + "indexmap", + "wit-parser 0.236.1", +] + [[package]] name = "wasmtime-wasi" -version = "33.0.2" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae951b72c7c6749a1c15dcdfb6d940a2614c932b4a54f474636e78e2c744b4c" +checksum = "c3c2e99fbaa0c26b4680e0c9af07e3f7b25f5fbc1ad97dd34067980bd027d3e5" dependencies = [ "anyhow", "async-trait", @@ -19759,14 +19797,14 @@ dependencies = [ "wasmtime", "wasmtime-wasi-io", "wiggle", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "wasmtime-wasi-io" -version = "33.0.2" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a835790dcecc3d7051ec67da52ba9e04af25e1bc204275b9391e3f0042b10797" +checksum = "de2dc367052562c228ce51ee4426330840433c29c0ea3349eca5ddeb475ecdb9" dependencies = [ "anyhow", "async-trait", @@ -19775,35 +19813,6 @@ dependencies = [ "wasmtime", ] -[[package]] -name = "wasmtime-winch" -version = "33.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc3b117d03d6eeabfa005a880c5c22c06503bb8820f3aa2e30f0e8d87b6752f" -dependencies = [ - "anyhow", - "cranelift-codegen", - "gimli 0.31.1", - "object 0.36.7", - "target-lexicon 0.13.3", - "wasmparser 0.229.0", - "wasmtime-cranelift", - "wasmtime-environ", - "winch-codegen", -] - -[[package]] -name = "wasmtime-wit-bindgen" -version = "33.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1382f4f09390eab0d75d4994d0c3b0f6279f86a571807ec67a8253c87cf6a145" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap", - "wit-parser 0.229.0", -] - [[package]] name = "wast" version = "35.0.2" @@ -20258,9 +20267,9 @@ dependencies = [ [[package]] name = "wiggle" -version = "33.0.2" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "649c1aca13ef9e9dccf2d5efbbebf12025bc5521c3fb7754355ef60f5eb810be" +checksum = "c13d1ae265bd6e5e608827d2535665453cae5cb64950de66e2d5767d3e32c43a" dependencies = [ "anyhow", "async-trait", @@ -20273,9 +20282,9 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "33.0.2" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164870fc34214ee42bd81b8ce9e7c179800fa1a7d4046d17a84e7f7bf422c8ad" +checksum = "607c4966f6b30da20d24560220137cbd09df722f0558eac81c05624700af5e05" dependencies = [ "anyhow", "heck 0.5.0", @@ -20287,9 +20296,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "33.0.2" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d873bb5b59ca703b5e41562e96a4796d1af61bf4cf80bf8a7abda755a380ec1c" +checksum = "fc36e39412fa35f7cc86b3705dbe154168721dd3e71f6dc4a726b266d5c60c55" dependencies = [ "proc-macro2", "quote", @@ -20330,21 +20339,22 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "33.0.2" +version = "36.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7914c296fbcef59d1b89a15e82384d34dc9669bc09763f2ef068a28dd3a64ebf" +checksum = "06c0ec09e8eb5e850e432da6271ed8c4a9d459a9db3850c38e98a3ee9d015e79" dependencies = [ "anyhow", "cranelift-assembler-x64", "cranelift-codegen", - "gimli 0.31.1", + "gimli", "regalloc2", "smallvec", "target-lexicon 0.13.3", "thiserror 2.0.17", - "wasmparser 0.229.0", - "wasmtime-cranelift", + "wasmparser 0.236.1", "wasmtime-environ", + "wasmtime-internal-cranelift", + "wasmtime-internal-math", ] [[package]] @@ -21370,9 +21380,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.229.0" +version = "0.236.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459c6ba62bf511d6b5f2a845a2a736822e38059c1cfa0b644b467bbbfae4efa6" +checksum = "16e4833a20cd6e85d6abfea0e63a399472d6f88c6262957c17f546879a80ba15" dependencies = [ "anyhow", "id-arena", @@ -21383,7 +21393,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.229.0", + "wasmparser 0.236.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6c8f2a78a401cc2adebb712cd8ce739c696af878..3a393237ab9f5a5a8cd4b02517f6d22382ff51ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -604,6 +604,7 @@ nbformat = "1.2.0" nix = "0.29" num-format = "0.4.4" objc = "0.2" +objc2-app-kit = { version = "0.3", default-features = false, features = [ "NSGraphics" ] } objc2-foundation = { version = "=0.3.2", default-features = false, features = [ "NSArray", "NSAttributedString", @@ -732,7 +733,7 @@ toml_edit = { version = "0.22", default-features = false, features = [ "serde", ] } tower-http = "0.4.4" -tree-sitter = { version = "0.26", features = ["wasm"] } +tree-sitter = { version = "0.26.8", features = ["wasm"] } tree-sitter-bash = "0.25.1" tree-sitter-c = "0.24.1" tree-sitter-cpp = { git = "https://github.com/tree-sitter/tree-sitter-cpp", rev = "5cb9b693cfd7bfacab1d9ff4acac1a4150700609" } @@ -767,7 +768,7 @@ uuid = { version = "1.1.2", features = ["v4", "v5", "v7", "serde"] } walkdir = "2.5" wasm-encoder = "0.221" wasmparser = "0.221" -wasmtime = { version = "33", default-features = false, features = [ +wasmtime = { version = "36", default-features = false, features = [ "async", "demangle", "runtime", @@ -776,7 +777,7 @@ wasmtime = { version = "33", default-features = false, features = [ "incremental-cache", "parallel-compilation", ] } -wasmtime-wasi = "33" +wasmtime-wasi = "36" wax = "0.7" which = "6.0.0" wasm-bindgen = "0.2.113" @@ -821,6 +822,7 @@ features = [ "Win32_System_Com", "Win32_System_Com_StructuredStorage", "Win32_System_Console", + "Win32_System_Diagnostics_Debug", "Win32_System_DataExchange", "Win32_System_IO", "Win32_System_LibraryLoader", diff --git a/assets/icons/maximize_alt.svg b/assets/icons/maximize_alt.svg new file mode 100644 index 0000000000000000000000000000000000000000..b8b8705f902c2469ed959f93f89ca3caf3b8fc51 --- /dev/null +++ b/assets/icons/maximize_alt.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/thread_import.svg b/assets/icons/thread_import.svg new file mode 100644 index 0000000000000000000000000000000000000000..a56b5a7cccc09c5795bfadff06f06d15833232f3 --- /dev/null +++ b/assets/icons/thread_import.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index e6ef267a95110e745534010bae32b1b1fd6c0f0c..a32f92942682fc0c5efbbcd35a9848c90b761184 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -21,8 +21,8 @@ use settings::{LanguageModelProviderSetting, LanguageModelSelection}; use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt as _}; use zed_actions::agent::{ - AddSelectionToThread, ConflictContent, OpenClaudeAgentOnboardingModal, ReauthenticateAgent, - ResolveConflictedFilesWithAgent, ResolveConflictsWithAgent, ReviewBranchDiff, + AddSelectionToThread, ConflictContent, ReauthenticateAgent, ResolveConflictedFilesWithAgent, + ResolveConflictsWithAgent, ReviewBranchDiff, }; use crate::{ @@ -40,7 +40,7 @@ use crate::{ }; use crate::{ DEFAULT_THREAD_TITLE, - ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal, HoldForDefault}, + ui::{AcpOnboardingModal, HoldForDefault}, }; use crate::{ExpandMessageEditor, ThreadHistoryView}; use crate::{ManageProfiles, ThreadHistoryViewEvent}; @@ -245,11 +245,6 @@ pub fn init(cx: &mut App) { .register_action(|workspace, _: &OpenAcpOnboardingModal, window, cx| { AcpOnboardingModal::toggle(workspace, window, cx) }) - .register_action( - |workspace, _: &OpenClaudeAgentOnboardingModal, window, cx| { - ClaudeCodeOnboardingModal::toggle(workspace, window, cx) - }, - ) .register_action(|_workspace, _: &ResetOnboarding, window, cx| { window.dispatch_action(workspace::RestoreBanner.boxed_clone(), cx); window.refresh(); diff --git a/crates/agent_ui/src/config_options.rs b/crates/agent_ui/src/config_options.rs index b8cf7e5d57921c7710392911829fc2b5045a0f90..44c0baa232222c0ba7c1d54acdecaabacfa85f12 100644 --- a/crates/agent_ui/src/config_options.rs +++ b/crates/agent_ui/src/config_options.rs @@ -650,7 +650,7 @@ impl PickerDelegate for ConfigOptionPickerDelegate { .end_slot(div().pr_2().when(is_selected, |this| { this.child(Icon::new(IconName::Check).color(Color::Accent)) })) - .end_hover_slot(div().pr_1p5().child({ + .end_slot_on_hover(div().pr_1p5().child({ let (icon, color, tooltip) = if is_favorite { (IconName::StarFilled, Color::Accent, "Unfavorite") } else { diff --git a/crates/agent_ui/src/thread_import.rs b/crates/agent_ui/src/thread_import.rs index 9dd6b5efa0ae1cd3bc19dc6ae6a287218de8c668..f5fc89d3df4991ff5186e2af6d73ad6a840c09a1 100644 --- a/crates/agent_ui/src/thread_import.rs +++ b/crates/agent_ui/src/thread_import.rs @@ -121,18 +121,6 @@ impl ThreadImportModal { .collect() } - fn set_agent_checked(&mut self, agent_id: AgentId, state: ToggleState, cx: &mut Context) { - match state { - ToggleState::Selected => { - self.unchecked_agents.remove(&agent_id); - } - ToggleState::Unselected | ToggleState::Indeterminate => { - self.unchecked_agents.insert(agent_id); - } - } - cx.notify(); - } - fn toggle_agent_checked(&mut self, agent_id: AgentId, cx: &mut Context) { if self.unchecked_agents.contains(&agent_id) { self.unchecked_agents.remove(&agent_id); @@ -283,6 +271,11 @@ impl ModalView for ThreadImportModal {} impl Render for ThreadImportModal { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + let has_agents = !self.agent_entries.is_empty(); + let disabled_import_thread = self.is_importing + || !has_agents + || self.unchecked_agents.len() == self.agent_entries.len(); + let agent_rows = self .agent_entries .iter() @@ -295,6 +288,7 @@ impl Render for ThreadImportModal { .rounded() .spacing(ListItemSpacing::Sparse) .focused(is_focused) + .disabled(self.is_importing) .child( h_flex() .w_full() @@ -311,22 +305,14 @@ impl Render for ThreadImportModal { }) .child(Label::new(entry.display_name.clone())), ) - .end_slot( - Checkbox::new( - ("thread-import-agent-checkbox", ix), - if is_checked { - ToggleState::Selected - } else { - ToggleState::Unselected - }, - ) - .on_click({ - let agent_id = entry.agent_id.clone(); - cx.listener(move |this, state: &ToggleState, _window, cx| { - this.set_agent_checked(agent_id.clone(), *state, cx); - }) - }), - ) + .end_slot(Checkbox::new( + ("thread-import-agent-checkbox", ix), + if is_checked { + ToggleState::Selected + } else { + ToggleState::Unselected + }, + )) .on_click({ let agent_id = entry.agent_id.clone(); cx.listener(move |this, _event, _window, cx| { @@ -336,11 +322,6 @@ impl Render for ThreadImportModal { }) .collect::>(); - let has_agents = !self.agent_entries.is_empty(); - let disabled_import_thread = self.is_importing - || !has_agents - || self.unchecked_agents.len() == self.agent_entries.len(); - v_flex() .id("thread-import-modal") .key_context("ThreadImportModal") @@ -373,7 +354,7 @@ impl Render for ThreadImportModal { v_flex() .id("thread-import-agent-list") .max_h(rems_from_px(320.)) - .pb_2() + .pb_1() .overflow_y_scroll() .when(has_agents, |this| this.children(agent_rows)) .when(!has_agents, |this| { diff --git a/crates/agent_ui/src/threads_archive_view.rs b/crates/agent_ui/src/threads_archive_view.rs index 445d86c9ad4e37fa8b2502a754a5264cd1d4dc45..74a93129d387e0aaac6e7092d9e086dd64e369f7 100644 --- a/crates/agent_ui/src/threads_archive_view.rs +++ b/crates/agent_ui/src/threads_archive_view.rs @@ -1,5 +1,5 @@ use crate::agent_connection_store::AgentConnectionStore; -use crate::thread_import::{AcpThreadImportOnboarding, ThreadImportModal}; + use crate::thread_metadata_store::{ThreadMetadata, ThreadMetadataStore}; use crate::{Agent, RemoveSelectedThread}; @@ -15,15 +15,13 @@ use gpui::{ }; use itertools::Itertools as _; use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrevious}; -use project::{AgentId, AgentRegistryStore, AgentServerStore}; +use project::{AgentId, AgentServerStore}; use settings::Settings as _; use theme::ActiveTheme; use ui::ThreadItem; use ui::{ Divider, KeyBinding, Tooltip, WithScrollbar, prelude::*, utils::platform_title_bar_height, }; -use util::ResultExt; -use workspace::{MultiWorkspace, Workspace}; use zed_actions::agents_sidebar::FocusSidebarFilter; use zed_actions::editor::{MoveDown, MoveUp}; @@ -114,18 +112,12 @@ pub struct ThreadsArchiveView { _refresh_history_task: Task<()>, agent_connection_store: WeakEntity, agent_server_store: WeakEntity, - agent_registry_store: WeakEntity, - workspace: WeakEntity, - multi_workspace: WeakEntity, } impl ThreadsArchiveView { pub fn new( agent_connection_store: WeakEntity, agent_server_store: WeakEntity, - agent_registry_store: WeakEntity, - workspace: WeakEntity, - multi_workspace: WeakEntity, window: &mut Window, cx: &mut Context, ) -> Self { @@ -184,11 +176,8 @@ impl ThreadsArchiveView { thread_metadata_store_subscription, ], _refresh_history_task: Task::ready(()), - agent_registry_store, agent_connection_store, agent_server_store, - workspace, - multi_workspace, }; this.update_items(cx); @@ -550,43 +539,6 @@ impl ThreadsArchiveView { .detach_and_log_err(cx); } - fn should_render_acp_import_onboarding(&self, cx: &App) -> bool { - let has_external_agents = self - .agent_server_store - .upgrade() - .map(|store| store.read(cx).has_external_agents()) - .unwrap_or(false); - - has_external_agents && !AcpThreadImportOnboarding::dismissed(cx) - } - - fn show_thread_import_modal(&mut self, window: &mut Window, cx: &mut Context) { - let Some(agent_server_store) = self.agent_server_store.upgrade() else { - return; - }; - let Some(agent_registry_store) = self.agent_registry_store.upgrade() else { - return; - }; - - let workspace_handle = self.workspace.clone(); - let multi_workspace = self.multi_workspace.clone(); - - self.workspace - .update(cx, |workspace, cx| { - workspace.toggle_modal(window, cx, |window, cx| { - ThreadImportModal::new( - agent_server_store, - agent_registry_store, - workspace_handle.clone(), - multi_workspace.clone(), - window, - cx, - ) - }); - }) - .log_err(); - } - fn render_header(&self, window: &Window, cx: &mut Context) -> impl IntoElement { let has_query = !self.filter_editor.read(cx).text(cx).is_empty(); let sidebar_on_left = matches!( @@ -729,28 +681,5 @@ impl Render for ThreadsArchiveView { .size_full() .child(self.render_header(window, cx)) .child(content) - .when(!self.should_render_acp_import_onboarding(cx), |this| { - this.child( - div() - .w_full() - .p_1p5() - .border_t_1() - .border_color(cx.theme().colors().border) - .child( - Button::new("import-acp", "Import ACP Threads") - .full_width() - .style(ButtonStyle::OutlinedCustom(cx.theme().colors().border)) - .label_size(LabelSize::Small) - .start_icon( - Icon::new(IconName::ArrowDown) - .size(IconSize::XSmall) - .color(Color::Muted), - ) - .on_click(cx.listener(|this, _, window, cx| { - this.show_thread_import_modal(window, cx); - })), - ), - ) - }) } } diff --git a/crates/agent_ui/src/ui.rs b/crates/agent_ui/src/ui.rs index 16732951ce67d76ca8d65259e309c4b81df30c3b..d43b7e4b043bcd1b155699c5eea3ca695585b94b 100644 --- a/crates/agent_ui/src/ui.rs +++ b/crates/agent_ui/src/ui.rs @@ -1,6 +1,5 @@ mod acp_onboarding_modal; mod agent_notification; -mod claude_agent_onboarding_modal; mod end_trial_upsell; mod hold_for_default; mod mention_crease; @@ -9,7 +8,6 @@ mod undo_reject_toast; pub use acp_onboarding_modal::*; pub use agent_notification::*; -pub use claude_agent_onboarding_modal::*; pub use end_trial_upsell::*; pub use hold_for_default::*; pub use mention_crease::*; diff --git a/crates/agent_ui/src/ui/claude_agent_onboarding_modal.rs b/crates/agent_ui/src/ui/claude_agent_onboarding_modal.rs deleted file mode 100644 index 5b7e58eb4fd79a5075446dad997c2642fedf32a6..0000000000000000000000000000000000000000 --- a/crates/agent_ui/src/ui/claude_agent_onboarding_modal.rs +++ /dev/null @@ -1,261 +0,0 @@ -use agent_servers::CLAUDE_AGENT_ID; -use gpui::{ - ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, MouseDownEvent, Render, - linear_color_stop, linear_gradient, -}; -use ui::{TintColor, Vector, VectorName, prelude::*}; -use workspace::{ModalView, Workspace}; - -use crate::{Agent, agent_panel::AgentPanel}; - -macro_rules! claude_agent_onboarding_event { - ($name:expr) => { - telemetry::event!($name, source = "ACP Claude Code Onboarding"); - }; - ($name:expr, $($key:ident $(= $value:expr)?),+ $(,)?) => { - telemetry::event!($name, source = "ACP Claude Code Onboarding", $($key $(= $value)?),+); - }; -} - -pub struct ClaudeCodeOnboardingModal { - focus_handle: FocusHandle, - workspace: Entity, -} - -impl ClaudeCodeOnboardingModal { - pub fn toggle(workspace: &mut Workspace, window: &mut Window, cx: &mut Context) { - let workspace_entity = cx.entity(); - workspace.toggle_modal(window, cx, |_window, cx| Self { - workspace: workspace_entity, - focus_handle: cx.focus_handle(), - }); - } - - fn open_panel(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context) { - self.workspace.update(cx, |workspace, cx| { - workspace.focus_panel::(window, cx); - - if let Some(panel) = workspace.panel::(cx) { - panel.update(cx, |panel, cx| { - panel.new_agent_thread( - Agent::Custom { - id: CLAUDE_AGENT_ID.into(), - }, - window, - cx, - ); - }); - } - }); - - cx.emit(DismissEvent); - - claude_agent_onboarding_event!("Open Panel Clicked"); - } - - fn view_docs(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context) { - window.dispatch_action(Box::new(zed_actions::AcpRegistry), cx); - cx.notify(); - - claude_agent_onboarding_event!("Documentation Link Clicked"); - } - - fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context) { - cx.emit(DismissEvent); - } -} - -impl EventEmitter for ClaudeCodeOnboardingModal {} - -impl Focusable for ClaudeCodeOnboardingModal { - fn focus_handle(&self, _cx: &App) -> FocusHandle { - self.focus_handle.clone() - } -} - -impl ModalView for ClaudeCodeOnboardingModal {} - -impl Render for ClaudeCodeOnboardingModal { - fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let illustration_element = |icon: IconName, label: Option, opacity: f32| { - h_flex() - .px_1() - .py_0p5() - .gap_1() - .rounded_sm() - .bg(cx.theme().colors().element_active.opacity(0.05)) - .border_1() - .border_color(cx.theme().colors().border) - .border_dashed() - .child( - Icon::new(icon) - .size(IconSize::Small) - .color(Color::Custom(cx.theme().colors().text_muted.opacity(0.15))), - ) - .map(|this| { - if let Some(label_text) = label { - this.child( - Label::new(label_text) - .size(LabelSize::Small) - .color(Color::Muted), - ) - } else { - this.child( - div().w_16().h_1().rounded_full().bg(cx - .theme() - .colors() - .element_active - .opacity(0.6)), - ) - } - }) - .opacity(opacity) - }; - - let illustration = h_flex() - .relative() - .h(rems_from_px(126.)) - .bg(cx.theme().colors().editor_background) - .border_b_1() - .border_color(cx.theme().colors().border_variant) - .justify_center() - .gap_8() - .rounded_t_md() - .overflow_hidden() - .child( - div().absolute().inset_0().w(px(515.)).h(px(126.)).child( - Vector::new(VectorName::AcpGrid, rems_from_px(515.), rems_from_px(126.)) - .color(ui::Color::Custom(cx.theme().colors().text.opacity(0.02))), - ), - ) - .child(div().absolute().inset_0().size_full().bg(linear_gradient( - 0., - linear_color_stop( - cx.theme().colors().elevated_surface_background.opacity(0.1), - 0.9, - ), - linear_color_stop( - cx.theme().colors().elevated_surface_background.opacity(0.), - 0., - ), - ))) - .child( - div() - .absolute() - .inset_0() - .size_full() - .bg(gpui::black().opacity(0.15)), - ) - .child( - Vector::new( - VectorName::AcpLogoSerif, - rems_from_px(257.), - rems_from_px(47.), - ) - .color(ui::Color::Custom(cx.theme().colors().text.opacity(0.8))), - ) - .child( - v_flex() - .gap_1p5() - .child(illustration_element(IconName::Stop, None, 0.15)) - .child(illustration_element( - IconName::AiGemini, - Some("New Gemini CLI Thread".into()), - 0.3, - )) - .child( - h_flex() - .pl_1() - .pr_2() - .py_0p5() - .gap_1() - .rounded_sm() - .bg(cx.theme().colors().element_active.opacity(0.2)) - .border_1() - .border_color(cx.theme().colors().border) - .child( - Icon::new(IconName::AiClaude) - .size(IconSize::Small) - .color(Color::Muted), - ) - .child(Label::new("New Claude Agent Thread").size(LabelSize::Small)), - ) - .child(illustration_element( - IconName::Stop, - Some("Your Agent Here".into()), - 0.3, - )) - .child(illustration_element(IconName::Stop, None, 0.15)), - ); - - let heading = v_flex() - .w_full() - .gap_1() - .child( - Label::new("Beta Release") - .size(LabelSize::Small) - .color(Color::Muted), - ) - .child(Headline::new("Claude Agent: Natively in Zed").size(HeadlineSize::Large)); - - let copy = "Powered by the Agent Client Protocol, you can now run Claude Agent as\na first-class citizen in Zed's agent panel."; - - let open_panel_button = Button::new("open-panel", "Start with Claude Agent") - .style(ButtonStyle::Tinted(TintColor::Accent)) - .full_width() - .on_click(cx.listener(Self::open_panel)); - - let docs_button = Button::new("add-other-agents", "Add Other Agents") - .end_icon( - Icon::new(IconName::ArrowUpRight) - .size(IconSize::Indicator) - .color(Color::Muted), - ) - .full_width() - .on_click(cx.listener(Self::view_docs)); - - let close_button = h_flex().absolute().top_2().right_2().child( - IconButton::new("cancel", IconName::Close).on_click(cx.listener( - |_, _: &ClickEvent, _window, cx| { - claude_agent_onboarding_event!("Canceled", trigger = "X click"); - cx.emit(DismissEvent); - }, - )), - ); - - v_flex() - .id("acp-onboarding") - .key_context("AcpOnboardingModal") - .relative() - .w(rems(34.)) - .h_full() - .elevation_3(cx) - .track_focus(&self.focus_handle(cx)) - .overflow_hidden() - .on_action(cx.listener(Self::cancel)) - .on_action(cx.listener(|_, _: &menu::Cancel, _window, cx| { - claude_agent_onboarding_event!("Canceled", trigger = "Action"); - cx.emit(DismissEvent); - })) - .on_any_mouse_down(cx.listener(|this, _: &MouseDownEvent, window, cx| { - this.focus_handle.focus(window, cx); - })) - .child(illustration) - .child( - v_flex() - .p_4() - .gap_2() - .child(heading) - .child(Label::new(copy).color(Color::Muted)) - .child( - v_flex() - .w_full() - .mt_2() - .gap_1() - .child(open_panel_button) - .child(docs_button), - ), - ) - .child(close_button) - } -} diff --git a/crates/agent_ui/src/ui/model_selector_components.rs b/crates/agent_ui/src/ui/model_selector_components.rs index 01ba6c4511854e83b97b1fc053e41e5d0e82ff1e..88bf546a0e7beef53c8043fd04f8e6e9e5e92c88 100644 --- a/crates/agent_ui/src/ui/model_selector_components.rs +++ b/crates/agent_ui/src/ui/model_selector_components.rs @@ -160,7 +160,7 @@ impl RenderOnce for ModelSelectorListItem { .end_slot(div().pr_2().when(self.is_selected, |this| { this.child(Icon::new(IconName::Check).color(Color::Accent)) })) - .end_hover_slot(div().pr_1p5().when_some(self.on_toggle_favorite, { + .end_slot_on_hover(div().pr_1p5().when_some(self.on_toggle_favorite, { |this, handle_click| { let (icon, color, tooltip) = if is_favorite { (IconName::StarFilled, Color::Accent, "Unfavorite Model") diff --git a/crates/collab/tests/integration/git_tests.rs b/crates/collab/tests/integration/git_tests.rs index cc96f86261f5e708c745ec232fbbe3fd1762d992..50ce3dddc7f606f1af0f6cb21b7b99d9a1e7c61e 100644 --- a/crates/collab/tests/integration/git_tests.rs +++ b/crates/collab/tests/integration/git_tests.rs @@ -401,16 +401,19 @@ async fn test_linked_worktrees_sync( path: PathBuf::from(path!("/project")), ref_name: Some("refs/heads/main".into()), sha: "aaa111".into(), + is_main: false, }); state.worktrees.push(GitWorktree { path: PathBuf::from(path!("/project/feature-branch")), ref_name: Some("refs/heads/feature-branch".into()), sha: "bbb222".into(), + is_main: false, }); state.worktrees.push(GitWorktree { path: PathBuf::from(path!("/project/bugfix-branch")), ref_name: Some("refs/heads/bugfix-branch".into()), sha: "ccc333".into(), + is_main: false, }); }) .unwrap(); @@ -480,6 +483,7 @@ async fn test_linked_worktrees_sync( path: PathBuf::from(path!("/project/hotfix-branch")), ref_name: Some("refs/heads/hotfix-branch".into()), sha: "ddd444".into(), + is_main: false, }); }) .unwrap(); diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 90ed7d0d3518aa4f6d49bb4cc18cbf3c275ce7c5..4a80740c3765f25ee878a60fa061c17e3a795b5f 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -43,24 +43,28 @@ pub struct CommandPalette { picker: Entity>, } -/// Removes subsequent whitespace characters and double colons from the query. +/// Removes subsequent whitespace characters and double colons from the query, and converts +/// underscores to spaces. /// /// This improves the likelihood of a match by either humanized name or keymap-style name. +/// Underscores are converted to spaces because `humanize_action_name` converts them to spaces +/// when building the search candidates (e.g. `terminal_panel::Toggle` -> `terminal panel: toggle`). pub fn normalize_action_query(input: &str) -> String { let mut result = String::with_capacity(input.len()); let mut last_char = None; for char in input.trim().chars() { - match (last_char, char) { + let normalized_char = if char == '_' { ' ' } else { char }; + match (last_char, normalized_char) { (Some(':'), ':') => continue, - (Some(last_char), char) if last_char.is_whitespace() && char.is_whitespace() => { + (Some(last_char), c) if last_char.is_whitespace() && c.is_whitespace() => { continue; } _ => { - last_char = Some(char); + last_char = Some(normalized_char); } } - result.push(char); + result.push(normalized_char); } result @@ -775,6 +779,14 @@ mod tests { normalize_action_query("editor: :GoToDefinition"), "editor: :GoToDefinition" ); + assert_eq!( + normalize_action_query("terminal_panel::Toggle"), + "terminal panel:Toggle" + ); + assert_eq!( + normalize_action_query("project_panel::ToggleFocus"), + "project panel:ToggleFocus" + ); } #[gpui::test] diff --git a/crates/edit_prediction/src/license_detection.rs b/crates/edit_prediction/src/license_detection.rs index 2b44825c4ceef1a317034966aa1a0b6a7a0f54c2..6f701d13a9d4d915bbfbc2442ea5643afac30ef4 100644 --- a/crates/edit_prediction/src/license_detection.rs +++ b/crates/edit_prediction/src/license_detection.rs @@ -308,7 +308,9 @@ impl LicenseDetectionWatcher { } } } - worktree::Event::DeletedEntry(_) | worktree::Event::UpdatedGitRepositories(_) => {} + worktree::Event::DeletedEntry(_) + | worktree::Event::UpdatedGitRepositories(_) + | worktree::Event::Deleted => {} }); let worktree_snapshot = worktree.read(cx).snapshot(); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 48c92f0f22762f95b1d6ef681951355a340d221e..65a872e6035565bb01fdd78e00d6cf0f35d35ef8 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -31952,6 +31952,65 @@ async fn test_sticky_scroll_with_expanded_deleted_diff_hunks( assert_eq!(sticky_headers(6.0), vec![]); } +#[gpui::test] +async fn test_no_duplicated_sticky_headers(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + let mut cx = EditorTestContext::new(cx).await; + + cx.set_state(indoc! {" + ˇimpl Foo { fn bar() { + let x = 1; + fn baz() { + let y = 2; + } + } } + "}); + + cx.update_editor(|e, _, cx| { + e.buffer() + .read(cx) + .as_singleton() + .unwrap() + .update(cx, |buffer, cx| { + buffer.set_language(Some(rust_lang()), cx); + }) + }); + + let mut sticky_headers = |offset: ScrollOffset| { + cx.update_editor(|e, window, cx| { + e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx); + }); + cx.run_until_parked(); + cx.update_editor(|e, window, cx| { + EditorElement::sticky_headers(&e, &e.snapshot(window, cx)) + .into_iter() + .map( + |StickyHeader { + start_point, + offset, + .. + }| { (start_point, offset) }, + ) + .collect::>() + }) + }; + + let struct_foo = Point { row: 0, column: 0 }; + let fn_baz = Point { row: 2, column: 4 }; + + assert_eq!(sticky_headers(0.0), vec![]); + assert_eq!(sticky_headers(0.5), vec![(struct_foo, 0.0)]); + assert_eq!(sticky_headers(1.0), vec![(struct_foo, 0.0)]); + assert_eq!(sticky_headers(1.5), vec![(struct_foo, 0.0), (fn_baz, 1.0)]); + assert_eq!(sticky_headers(2.0), vec![(struct_foo, 0.0), (fn_baz, 1.0)]); + assert_eq!(sticky_headers(2.5), vec![(struct_foo, 0.0), (fn_baz, 0.5)]); + assert_eq!(sticky_headers(3.0), vec![(struct_foo, 0.0)]); + assert_eq!(sticky_headers(3.5), vec![(struct_foo, 0.0)]); + assert_eq!(sticky_headers(4.0), vec![(struct_foo, 0.0)]); + assert_eq!(sticky_headers(4.5), vec![(struct_foo, -0.5)]); + assert_eq!(sticky_headers(5.0), vec![]); +} + #[gpui::test] fn test_relative_line_numbers(cx: &mut TestAppContext) { init_test(cx, |_| {}); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 968048f68513a09c460bb06789103923bbbca828..9ce080c87bf82ec1098e2a4b1db6bc6a65d22828 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -4674,6 +4674,13 @@ impl EditorElement { .display_snapshot .point_to_display_point(start_point, Bias::Left) .row(); + if rows + .last() + .is_some_and(|last| last.sticky_row == sticky_row) + { + continue; + } + let end_row = snapshot .display_snapshot .point_to_display_point(end_point, Bias::Left) diff --git a/crates/extension_host/src/wasm_host.rs b/crates/extension_host/src/wasm_host.rs index 286639cdd67d716b1137290baf269670ecddebe7..87a2032e831fc942f6848428a901a9fe3f613fc8 100644 --- a/crates/extension_host/src/wasm_host.rs +++ b/crates/extension_host/src/wasm_host.rs @@ -42,7 +42,7 @@ use wasmtime::{ CacheStore, Engine, Store, component::{Component, ResourceTable}, }; -use wasmtime_wasi::p2::{self as wasi, IoView as _}; +use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; use wit::Extension; pub struct WasmHost { @@ -93,7 +93,7 @@ impl extension::Extension for WasmExtension { ) -> Result { self.call(|extension, store| { async move { - let resource = store.data_mut().table().push(worktree)?; + let resource = store.data_mut().table.push(worktree)?; let command = extension .call_language_server_command( store, @@ -119,7 +119,7 @@ impl extension::Extension for WasmExtension { ) -> Result> { self.call(|extension, store| { async move { - let resource = store.data_mut().table().push(worktree)?; + let resource = store.data_mut().table.push(worktree)?; let options = extension .call_language_server_initialization_options( store, @@ -143,7 +143,7 @@ impl extension::Extension for WasmExtension { ) -> Result> { self.call(|extension, store| { async move { - let resource = store.data_mut().table().push(worktree)?; + let resource = store.data_mut().table.push(worktree)?; let options = extension .call_language_server_workspace_configuration( store, @@ -166,7 +166,7 @@ impl extension::Extension for WasmExtension { ) -> Result> { self.call(|extension, store| { async move { - let resource = store.data_mut().table().push(worktree)?; + let resource = store.data_mut().table.push(worktree)?; extension .call_language_server_initialization_options_schema( store, @@ -187,7 +187,7 @@ impl extension::Extension for WasmExtension { ) -> Result> { self.call(|extension, store| { async move { - let resource = store.data_mut().table().push(worktree)?; + let resource = store.data_mut().table.push(worktree)?; extension .call_language_server_workspace_configuration_schema( store, @@ -209,7 +209,7 @@ impl extension::Extension for WasmExtension { ) -> Result> { self.call(|extension, store| { async move { - let resource = store.data_mut().table().push(worktree)?; + let resource = store.data_mut().table.push(worktree)?; let options = extension .call_language_server_additional_initialization_options( store, @@ -234,7 +234,7 @@ impl extension::Extension for WasmExtension { ) -> Result> { self.call(|extension, store| { async move { - let resource = store.data_mut().table().push(worktree)?; + let resource = store.data_mut().table.push(worktree)?; let options = extension .call_language_server_additional_workspace_configuration( store, @@ -331,7 +331,7 @@ impl extension::Extension for WasmExtension { self.call(|extension, store| { async move { let resource = if let Some(delegate) = delegate { - Some(store.data_mut().table().push(delegate)?) + Some(store.data_mut().table.push(delegate)?) } else { None }; @@ -355,7 +355,7 @@ impl extension::Extension for WasmExtension { ) -> Result { self.call(|extension, store| { async move { - let project_resource = store.data_mut().table().push(project)?; + let project_resource = store.data_mut().table.push(project)?; let command = extension .call_context_server_command(store, context_server_id.clone(), project_resource) .await? @@ -374,7 +374,7 @@ impl extension::Extension for WasmExtension { ) -> Result> { self.call(|extension, store| { async move { - let project_resource = store.data_mut().table().push(project)?; + let project_resource = store.data_mut().table.push(project)?; let Some(configuration) = extension .call_context_server_configuration( store, @@ -417,7 +417,7 @@ impl extension::Extension for WasmExtension { ) -> Result<()> { self.call(|extension, store| { async move { - let kv_store_resource = store.data_mut().table().push(kv_store)?; + let kv_store_resource = store.data_mut().table.push(kv_store)?; extension .call_index_docs( store, @@ -444,7 +444,7 @@ impl extension::Extension for WasmExtension { ) -> Result { self.call(|extension, store| { async move { - let resource = store.data_mut().table().push(worktree)?; + let resource = store.data_mut().table.push(worktree)?; let dap_binary = extension .call_get_dap_binary(store, dap_name, config, user_installed_path, resource) .await? @@ -532,7 +532,7 @@ impl extension::Extension for WasmExtension { pub struct WasmState { manifest: Arc, pub table: ResourceTable, - ctx: wasi::WasiCtx, + ctx: WasiCtx, pub host: Arc, pub(crate) capability_granter: CapabilityGranter, } @@ -726,7 +726,7 @@ impl WasmHost { }) } - async fn build_wasi_ctx(&self, manifest: &Arc) -> Result { + async fn build_wasi_ctx(&self, manifest: &Arc) -> Result { let extension_work_dir = self.work_dir.join(manifest.id.as_ref()); self.fs .create_dir(&extension_work_dir) @@ -739,7 +739,7 @@ impl WasmHost { #[cfg(target_os = "windows")] let path = path.replace('\\', "/"); - let mut ctx = wasi::WasiCtxBuilder::new(); + let mut ctx = WasiCtxBuilder::new(); ctx.inherit_stdio() .env("PWD", &path) .env("RUST_BACKTRACE", "full"); @@ -947,15 +947,16 @@ impl WasmState { } } -impl wasi::IoView for WasmState { - fn table(&mut self) -> &mut ResourceTable { - &mut self.table - } +impl wasmtime::component::HasData for WasmState { + type Data<'a> = &'a mut WasmState; } -impl wasi::WasiView for WasmState { - fn ctx(&mut self) -> &mut wasi::WasiCtx { - &mut self.ctx +impl WasiView for WasmState { + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.ctx, + table: &mut self.table, + } } } diff --git a/crates/extension_host/src/wasm_host/wit.rs b/crates/extension_host/src/wasm_host/wit.rs index 9c4d3aa298c366ae91d0f8195ed090d74099c6d0..27847422f01680240119877e0864491dd7660d68 100644 --- a/crates/extension_host/src/wasm_host/wit.rs +++ b/crates/extension_host/src/wasm_host/wit.rs @@ -42,18 +42,14 @@ pub use since_v0_0_4::LanguageServerConfig; pub fn new_linker( executor: &BackgroundExecutor, - f: impl Fn(&mut Linker, fn(&mut WasmState) -> &mut WasmState) -> Result<()>, + f: impl FnOnce(&mut Linker) -> Result<()>, ) -> Linker { let mut linker = Linker::new(&wasm_engine(executor)); wasmtime_wasi::p2::add_to_linker_async(&mut linker).unwrap(); - f(&mut linker, wasi_view).unwrap(); + f(&mut linker).unwrap(); linker } -fn wasi_view(state: &mut WasmState) -> &mut WasmState { - state -} - /// Returns whether the given Wasm API version is supported by the Wasm host. pub fn is_supported_wasm_api_version(release_channel: ReleaseChannel, version: Version) -> bool { wasm_api_version_range(release_channel).contains(&version) 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 fa7539eec9f454c95782cd0249664693074abfba..c231b7e5d69157d523973455b2437a576392a00d 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 @@ -12,8 +12,12 @@ use wasmtime::component::{Linker, Resource}; pub const MIN_VERSION: Version = Version::new(0, 0, 1); wasmtime::component::bindgen!({ - async: true, - trappable_imports: true, + imports: { + default: async | trappable, + }, + exports: { + default: async, + }, path: "../extension_api/wit/since_v0.0.1", with: { "worktree": ExtensionWorktree, @@ -26,7 +30,11 @@ pub type ExtensionWorktree = Arc; pub fn linker(executor: &BackgroundExecutor) -> &'static Linker { static LINKER: OnceLock> = OnceLock::new(); - LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker)) + LINKER.get_or_init(|| { + super::new_linker(executor, |linker| { + Extension::add_to_linker::<_, WasmState>(linker, |s| s) + }) + }) } impl From for latest::DownloadedFileType { diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_0_4.rs b/crates/extension_host/src/wasm_host/wit/since_v0_0_4.rs index 6d7db749f0cd021bfb084eba1bc20ce72780f3d8..41d652cec3087e8e5458a048689be4494de63356 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_0_4.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_0_4.rs @@ -10,8 +10,12 @@ use wasmtime::component::{Linker, Resource}; pub const MIN_VERSION: Version = Version::new(0, 0, 4); wasmtime::component::bindgen!({ - async: true, - trappable_imports: true, + imports: { + default: async | trappable, + }, + exports: { + default: async, + }, path: "../extension_api/wit/since_v0.0.4", with: { "worktree": ExtensionWorktree, @@ -24,7 +28,11 @@ pub type ExtensionWorktree = Arc; pub fn linker(executor: &BackgroundExecutor) -> &'static Linker { static LINKER: OnceLock> = OnceLock::new(); - LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker)) + LINKER.get_or_init(|| { + super::new_linker(executor, |linker| { + Extension::add_to_linker::<_, WasmState>(linker, |s| s) + }) + }) } impl From for latest::DownloadedFileType { diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_0_6.rs b/crates/extension_host/src/wasm_host/wit/since_v0_0_6.rs index e5ff0322088470d47e903c4a83794b654bbba531..e1dfdf8248b41de2de5e9faff3d212d06f1349c4 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_0_6.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_0_6.rs @@ -10,8 +10,12 @@ use wasmtime::component::{Linker, Resource}; pub const MIN_VERSION: Version = Version::new(0, 0, 6); wasmtime::component::bindgen!({ - async: true, - trappable_imports: true, + imports: { + default: async | trappable, + }, + exports: { + default: async, + }, path: "../extension_api/wit/since_v0.0.6", with: { "worktree": ExtensionWorktree, @@ -31,7 +35,11 @@ pub type ExtensionWorktree = Arc; pub fn linker(executor: &BackgroundExecutor) -> &'static Linker { static LINKER: OnceLock> = OnceLock::new(); - LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker)) + LINKER.get_or_init(|| { + super::new_linker(executor, |linker| { + Extension::add_to_linker::<_, WasmState>(linker, |s| s) + }) + }) } impl From for latest::Command { 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 0caaa86c2413f1b279319eeea4d8577d1ed4b5a5..4cd034d4d6af02971468ba8e57e1eebf9078353f 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 @@ -26,8 +26,12 @@ use super::{latest, since_v0_6_0}; pub const MIN_VERSION: Version = Version::new(0, 1, 0); wasmtime::component::bindgen!({ - async: true, - trappable_imports: true, + imports: { + default: async | trappable, + }, + exports: { + default: async, + }, path: "../extension_api/wit/since_v0.1.0", with: { "worktree": ExtensionWorktree, @@ -52,7 +56,11 @@ pub type ExtensionHttpResponseStream = Arc &'static Linker { static LINKER: OnceLock> = OnceLock::new(); - LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker)) + LINKER.get_or_init(|| { + super::new_linker(executor, |linker| { + Extension::add_to_linker::<_, WasmState>(linker, |s| s) + }) + }) } impl From for latest::Command { 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 074cce73c22d547cd3198a672e6f8cdc5f750d49..691e6d2dd549b64c3783406af210b6b48f4a1dbc 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 @@ -11,8 +11,12 @@ use super::{latest, since_v0_6_0}; pub const MIN_VERSION: Version = Version::new(0, 2, 0); wasmtime::component::bindgen!({ - async: true, - trappable_imports: true, + imports: { + default: async | trappable, + }, + exports: { + default: async, + }, path: "../extension_api/wit/since_v0.2.0", with: { "worktree": ExtensionWorktree, @@ -40,7 +44,11 @@ pub type ExtensionKeyValueStore = Arc; pub fn linker(executor: &BackgroundExecutor) -> &'static Linker { static LINKER: OnceLock> = OnceLock::new(); - LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker)) + LINKER.get_or_init(|| { + super::new_linker(executor, |linker| { + Extension::add_to_linker::<_, WasmState>(linker, |s| s) + }) + }) } impl From for latest::Command { diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs index 072ad42f2b9c2f5b3a8556b237f3907052665370..53aa65d5187663ea86fa465af76cf3aebc7844e4 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs @@ -11,8 +11,12 @@ use super::{latest, since_v0_6_0}; pub const MIN_VERSION: Version = Version::new(0, 3, 0); wasmtime::component::bindgen!({ - async: true, - trappable_imports: true, + imports: { + default: async | trappable, + }, + exports: { + default: async, + }, path: "../extension_api/wit/since_v0.3.0", with: { "worktree": ExtensionWorktree, @@ -40,7 +44,11 @@ pub type ExtensionKeyValueStore = Arc; pub fn linker(executor: &BackgroundExecutor) -> &'static Linker { static LINKER: OnceLock> = OnceLock::new(); - LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker)) + LINKER.get_or_init(|| { + super::new_linker(executor, |linker| { + Extension::add_to_linker::<_, WasmState>(linker, |s| s) + }) + }) } impl From for latest::CodeLabel { diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_4_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_4_0.rs index 4f1d5c6a48c13ff09a5c81e2b43683fa50a7ccec..44b7d7ba1ad4e3235e8772a051bb906f87c64325 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_4_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_4_0.rs @@ -11,8 +11,12 @@ use super::{latest, since_v0_6_0}; pub const MIN_VERSION: Version = Version::new(0, 4, 0); wasmtime::component::bindgen!({ - async: true, - trappable_imports: true, + imports: { + default: async | trappable, + }, + exports: { + default: async, + }, path: "../extension_api/wit/since_v0.4.0", with: { "worktree": ExtensionWorktree, @@ -40,7 +44,11 @@ pub type ExtensionKeyValueStore = Arc; pub fn linker(executor: &BackgroundExecutor) -> &'static Linker { static LINKER: OnceLock> = OnceLock::new(); - LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker)) + LINKER.get_or_init(|| { + super::new_linker(executor, |linker| { + Extension::add_to_linker::<_, WasmState>(linker, |s| s) + }) + }) } impl From for latest::CodeLabel { diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_5_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_5_0.rs index 84f73f567750081d406b20025f0b4598cfd0f9af..4dff0d90a94fe1128c6182592093b38cf43fe573 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_5_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_5_0.rs @@ -11,8 +11,12 @@ use super::{latest, since_v0_6_0}; pub const MIN_VERSION: Version = Version::new(0, 5, 0); wasmtime::component::bindgen!({ - async: true, - trappable_imports: true, + imports: { + default: async | trappable, + }, + exports: { + default: async, + }, path: "../extension_api/wit/since_v0.5.0", with: { "worktree": ExtensionWorktree, @@ -41,7 +45,11 @@ pub type ExtensionKeyValueStore = Arc; pub fn linker(executor: &BackgroundExecutor) -> &'static Linker { static LINKER: OnceLock> = OnceLock::new(); - LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker)) + LINKER.get_or_init(|| { + super::new_linker(executor, |linker| { + Extension::add_to_linker::<_, WasmState>(linker, |s| s) + }) + }) } impl From for latest::CodeLabel { diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs index 202bcd6ce959b27b3b7ecf8e15830cb1955ec104..bc5674b051772e464c0cbdb74e75f935959e05d8 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs @@ -12,8 +12,12 @@ pub const MIN_VERSION: Version = Version::new(0, 6, 0); pub const MAX_VERSION: Version = Version::new(0, 7, 0); wasmtime::component::bindgen!({ - async: true, - trappable_imports: true, + imports: { + default: async | trappable, + }, + exports: { + default: async, + }, path: "../extension_api/wit/since_v0.6.0", with: { "worktree": ExtensionWorktree, @@ -43,7 +47,11 @@ pub type ExtensionKeyValueStore = Arc; pub fn linker(executor: &BackgroundExecutor) -> &'static Linker { static LINKER: OnceLock> = OnceLock::new(); - LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker)) + LINKER.get_or_init(|| { + super::new_linker(executor, |linker| { + Extension::add_to_linker::<_, WasmState>(linker, |s| s) + }) + }) } impl From for latest::CodeLabel { diff --git a/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs b/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs index 324a572f40c98037816870c99151a4789793da1b..660ddd9688f7dc69f3ec3c52452122fd807257ad 100644 --- a/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs +++ b/crates/extension_host/src/wasm_host/wit/since_v0_8_0.rs @@ -40,8 +40,12 @@ pub const MIN_VERSION: Version = Version::new(0, 8, 0); pub const MAX_VERSION: Version = Version::new(0, 8, 0); wasmtime::component::bindgen!({ - async: true, - trappable_imports: true, + imports: { + default: async | trappable, + }, + exports: { + default: async, + }, path: "../extension_api/wit/since_v0.8.0", with: { "worktree": ExtensionWorktree, @@ -65,7 +69,11 @@ pub type ExtensionHttpResponseStream = Arc &'static Linker { static LINKER: OnceLock> = OnceLock::new(); - LINKER.get_or_init(|| super::new_linker(executor, Extension::add_to_linker)) + LINKER.get_or_init(|| { + super::new_linker(executor, |linker| { + Extension::add_to_linker::<_, WasmState>(linker, |s| s) + }) + }) } impl From for std::ops::Range { diff --git a/crates/fs/src/fake_git_repo.rs b/crates/fs/src/fake_git_repo.rs index 998bb7d37896786b043ff4551131da09e00d872a..136419a35ae1ac656c1fe82a3fb63019b8f74518 100644 --- a/crates/fs/src/fake_git_repo.rs +++ b/crates/fs/src/fake_git_repo.rs @@ -481,6 +481,7 @@ impl GitRepository for FakeGitRepository { path: work_dir, ref_name: Some(branch_ref.into()), sha: head_sha.into(), + is_main: true, }; let mut all = vec![main_worktree]; all.extend(state.worktrees.iter().cloned()); @@ -523,6 +524,7 @@ impl GitRepository for FakeGitRepository { path, ref_name: Some(ref_name.into()), sha: sha.into(), + is_main: false, }); state.branches.insert(branch_name); } else { @@ -530,6 +532,7 @@ impl GitRepository for FakeGitRepository { path, ref_name: None, sha: sha.into(), + is_main: false, }); } Ok::<(), anyhow::Error>(()) diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs index bc7ba4329bed1e926e1aec0ef81422342711376b..497f31094e95091db0c819a1aeed63eebf61f554 100644 --- a/crates/git/src/repository.rs +++ b/crates/git/src/repository.rs @@ -238,6 +238,7 @@ pub struct Worktree { pub ref_name: Option, // todo(git_worktree) This type should be a Oid pub sha: SharedString, + pub is_main: bool, } impl Worktree { @@ -259,6 +260,7 @@ impl Worktree { pub fn parse_worktrees_from_str>(raw_worktrees: T) -> Vec { let mut worktrees = Vec::new(); + let mut is_first = true; let normalized = raw_worktrees.as_ref().replace("\r\n", "\n"); let entries = normalized.split("\n\n"); for entry in entries { @@ -286,7 +288,9 @@ pub fn parse_worktrees_from_str>(raw_worktrees: T) -> Vec(); repo.update(cx, |repo, cx| { diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs index 438df6839949d46d3ba8e0509995beb1300b7c80..83c8119a077ac1c024dbb3b3df948f762b072ec1 100644 --- a/crates/git_ui/src/branch_picker.rs +++ b/crates/git_ui/src/branch_picker.rs @@ -1087,13 +1087,8 @@ impl PickerDelegate for BranchListDelegate { ), ) .when(!is_new_items && !is_head_branch, |this| { - this.map(|this| { - if self.selected_index() == ix { - this.end_slot(deleted_branch_icon(ix)) - } else { - this.end_hover_slot(deleted_branch_icon(ix)) - } - }) + this.end_slot(deleted_branch_icon(ix)) + .show_end_slot_on_hover() }) .when_some( if is_new_items { @@ -1102,13 +1097,8 @@ impl PickerDelegate for BranchListDelegate { None }, |this, create_from_default_button| { - this.map(|this| { - if self.selected_index() == ix { - this.end_slot(create_from_default_button) - } else { - this.end_hover_slot(create_from_default_button) - } - }) + this.end_slot(create_from_default_button) + .show_end_slot_on_hover() }, ), ) diff --git a/crates/git_ui/src/stash_picker.rs b/crates/git_ui/src/stash_picker.rs index 9987190f45b73f3f1132ce1295de6f412022abe2..2d3515e833e4d353c323f533f1f0f39bb1d76561 100644 --- a/crates/git_ui/src/stash_picker.rs +++ b/crates/git_ui/src/stash_picker.rs @@ -501,16 +501,39 @@ impl PickerDelegate for StashListDelegate { .size(LabelSize::Small), ); - let focus_handle = self.focus_handle.clone(); + let view_button = { + let focus_handle = self.focus_handle.clone(); + IconButton::new(("view-stash", ix), IconName::Eye) + .icon_size(IconSize::Small) + .tooltip(move |_, cx| { + Tooltip::for_action_in("View Stash", &ShowStashItem, &focus_handle, cx) + }) + .on_click(cx.listener(move |this, _, window, cx| { + this.delegate.show_stash_at(ix, window, cx); + })) + }; + + let pop_button = { + let focus_handle = self.focus_handle.clone(); + IconButton::new(("pop-stash", ix), IconName::MaximizeAlt) + .icon_size(IconSize::Small) + .tooltip(move |_, cx| { + Tooltip::for_action_in("Pop Stash", &menu::SecondaryConfirm, &focus_handle, cx) + }) + .on_click(|_, window, cx| { + window.dispatch_action(menu::SecondaryConfirm.boxed_clone(), cx); + }) + }; - let drop_button = |entry_ix: usize| { - IconButton::new(("drop-stash", entry_ix), IconName::Trash) + let drop_button = { + let focus_handle = self.focus_handle.clone(); + IconButton::new(("drop-stash", ix), IconName::Trash) .icon_size(IconSize::Small) .tooltip(move |_, cx| { Tooltip::for_action_in("Drop Stash", &DropStashItem, &focus_handle, cx) }) .on_click(cx.listener(move |this, _, window, cx| { - this.delegate.drop_stash_at(entry_ix, window, cx); + this.delegate.drop_stash_at(ix, window, cx); })) }; @@ -530,17 +553,14 @@ impl PickerDelegate for StashListDelegate { ) .child(div().w_full().child(stash_label).child(branch_info)), ) - .tooltip(Tooltip::text(format!( - "stash@{{{}}}", - entry_match.entry.index - ))) - .map(|this| { - if selected { - this.end_slot(drop_button(ix)) - } else { - this.end_hover_slot(drop_button(ix)) - } - }), + .end_slot( + h_flex() + .gap_0p5() + .child(view_button) + .child(pop_button) + .child(drop_button), + ) + .show_end_slot_on_hover(), ) } @@ -549,6 +569,10 @@ impl PickerDelegate for StashListDelegate { } fn render_footer(&self, _: &mut Window, cx: &mut Context>) -> Option { + if self.matches.is_empty() { + return None; + } + let focus_handle = self.focus_handle.clone(); Some( diff --git a/crates/git_ui/src/worktree_picker.rs b/crates/git_ui/src/worktree_picker.rs index 9486dbc7b6ddb94e59f9da44069d55d8709fbea7..c3e2259e411c7a3a56a36b92735f8d5e014e53d7 100644 --- a/crates/git_ui/src/worktree_picker.rs +++ b/crates/git_ui/src/worktree_picker.rs @@ -254,6 +254,14 @@ struct WorktreeEntry { is_new: bool, } +impl WorktreeEntry { + fn can_delete(&self, forbidden_deletion_path: Option<&PathBuf>) -> bool { + !self.is_new + && !self.worktree.is_main + && forbidden_deletion_path != Some(&self.worktree.path) + } +} + pub struct WorktreeListDelegate { matches: Vec, all_worktrees: Option>, @@ -462,7 +470,7 @@ impl WorktreeListDelegate { let Some(entry) = self.matches.get(idx).cloned() else { return; }; - if entry.is_new || self.forbidden_deletion_path.as_ref() == Some(&entry.worktree.path) { + if !entry.can_delete(self.forbidden_deletion_path.as_ref()) { return; } let Some(repo) = self.repo.clone() else { @@ -719,6 +727,7 @@ impl PickerDelegate for WorktreeListDelegate { path: Default::default(), ref_name: Some(format!("refs/heads/{query}").into()), sha: Default::default(), + is_main: false, }, positions: Vec::new(), is_new: true, @@ -805,8 +814,7 @@ impl PickerDelegate for WorktreeListDelegate { let focus_handle = self.focus_handle.clone(); - let can_delete = - !entry.is_new && self.forbidden_deletion_path.as_ref() != Some(&entry.worktree.path); + let can_delete = entry.can_delete(self.forbidden_deletion_path.as_ref()); let delete_button = |entry_ix: usize| { IconButton::new(("delete-worktree", entry_ix), IconName::Trash) @@ -876,12 +884,30 @@ impl PickerDelegate for WorktreeListDelegate { } })), ) - .when(can_delete, |this| { - if selected { - this.end_slot(delete_button(ix)) - } else { - this.end_hover_slot(delete_button(ix)) - } + .when(!entry.is_new, |this| { + let focus_handle = self.focus_handle.clone(); + let open_in_new_window_button = + IconButton::new(("open-new-window", ix), IconName::ArrowUpRight) + .icon_size(IconSize::Small) + .tooltip(move |_, cx| { + Tooltip::for_action_in( + "Open in New Window", + &menu::SecondaryConfirm, + &focus_handle, + cx, + ) + }) + .on_click(|_, window, cx| { + window.dispatch_action(menu::SecondaryConfirm.boxed_clone(), cx); + }); + + this.end_slot( + h_flex() + .gap_0p5() + .child(open_in_new_window_button) + .when(can_delete, |this| this.child(delete_button(ix))), + ) + .show_end_slot_on_hover() }), ) } @@ -894,9 +920,8 @@ impl PickerDelegate for WorktreeListDelegate { let focus_handle = self.focus_handle.clone(); let selected_entry = self.matches.get(self.selected_index); let is_creating = selected_entry.is_some_and(|entry| entry.is_new); - let can_delete = selected_entry.is_some_and(|entry| { - !entry.is_new && self.forbidden_deletion_path.as_ref() != Some(&entry.worktree.path) - }); + let can_delete = selected_entry + .is_some_and(|entry| entry.can_delete(self.forbidden_deletion_path.as_ref())); let footer_container = h_flex() .w_full() diff --git a/crates/gpui/examples/input.rs b/crates/gpui/examples/input.rs index d15d791cd008883506389cc7bb16dbad765969c0..370e27de7d54c317af6683c240f343e750c68698 100644 --- a/crates/gpui/examples/input.rs +++ b/crates/gpui/examples/input.rs @@ -85,14 +85,24 @@ impl TextInput { fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context) { if self.selected_range.is_empty() { - self.select_to(self.previous_boundary(self.cursor_offset()), cx) + let prev = self.previous_boundary(self.cursor_offset()); + if self.cursor_offset() == prev { + window.play_system_bell(); + return; + } + self.select_to(prev, cx) } self.replace_text_in_range(None, "", window, cx) } fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context) { if self.selected_range.is_empty() { - self.select_to(self.next_boundary(self.cursor_offset()), cx) + let next = self.next_boundary(self.cursor_offset()); + if self.cursor_offset() == next { + window.play_system_bell(); + return; + } + self.select_to(next, cx) } self.replace_text_in_range(None, "", window, cx) } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 806a34040a4ec685c3d5c6ec01f47b5026e349a6..efca26a6b4802037a96490bf81f7d1c5c1d8b298 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -689,6 +689,8 @@ pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle { fn update_ime_position(&self, _bounds: Bounds); + fn play_system_bell(&self) {} + #[cfg(any(test, feature = "test-support"))] fn as_test(&mut self) -> Option<&mut TestWindow> { None diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 48c381e5275e950bd6754541fedbab03ae3d64c2..7790480e32149fa33dfd082df7a8cdbb09568134 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -5024,6 +5024,12 @@ impl Window { .set_tabbing_identifier(tabbing_identifier) } + /// Request the OS to play an alert sound. On some platforms this is associated + /// with the window, for others it's just a simple global function call. + pub fn play_system_bell(&self) { + self.platform_window.play_system_bell() + } + /// Toggles the inspector mode on this window. #[cfg(any(feature = "inspector", debug_assertions))] pub fn toggle_inspector(&mut self, cx: &mut App) { diff --git a/crates/gpui_linux/src/linux/wayland/client.rs b/crates/gpui_linux/src/linux/wayland/client.rs index b65a203dd3448ba191b7e2f5ae0f5b6c396545a8..10f4aab0db19978302143519dd6e2a7e4d25ec4d 100644 --- a/crates/gpui_linux/src/linux/wayland/client.rs +++ b/crates/gpui_linux/src/linux/wayland/client.rs @@ -58,6 +58,7 @@ use wayland_protocols::xdg::decoration::zv1::client::{ zxdg_decoration_manager_v1, zxdg_toplevel_decoration_v1, }; use wayland_protocols::xdg::shell::client::{xdg_surface, xdg_toplevel, xdg_wm_base}; +use wayland_protocols::xdg::system_bell::v1::client::xdg_system_bell_v1; use wayland_protocols::{ wp::cursor_shape::v1::client::{wp_cursor_shape_device_v1, wp_cursor_shape_manager_v1}, xdg::dialog::v1::client::xdg_wm_dialog_v1::{self, XdgWmDialogV1}, @@ -129,6 +130,7 @@ pub struct Globals { pub text_input_manager: Option, pub gesture_manager: Option, pub dialog: Option, + pub system_bell: Option, pub executor: ForegroundExecutor, } @@ -170,6 +172,7 @@ impl Globals { text_input_manager: globals.bind(&qh, 1..=1, ()).ok(), gesture_manager: globals.bind(&qh, 1..=3, ()).ok(), dialog: globals.bind(&qh, dialog_v..=dialog_v, ()).ok(), + system_bell: globals.bind(&qh, 1..=1, ()).ok(), executor, qh, } @@ -1069,6 +1072,7 @@ impl Dispatch for WaylandClientStat } delegate_noop!(WaylandClientStatePtr: ignore xdg_activation_v1::XdgActivationV1); +delegate_noop!(WaylandClientStatePtr: ignore xdg_system_bell_v1::XdgSystemBellV1); delegate_noop!(WaylandClientStatePtr: ignore wl_compositor::WlCompositor); delegate_noop!(WaylandClientStatePtr: ignore wp_cursor_shape_device_v1::WpCursorShapeDeviceV1); delegate_noop!(WaylandClientStatePtr: ignore wp_cursor_shape_manager_v1::WpCursorShapeManagerV1); diff --git a/crates/gpui_linux/src/linux/wayland/window.rs b/crates/gpui_linux/src/linux/wayland/window.rs index c4ff55fc80cc4d14069dd510b8e6855c17096773..1e3af66c59858c435ca3da093a1c48056b77667e 100644 --- a/crates/gpui_linux/src/linux/wayland/window.rs +++ b/crates/gpui_linux/src/linux/wayland/window.rs @@ -1479,6 +1479,18 @@ impl PlatformWindow for WaylandWindow { fn gpu_specs(&self) -> Option { self.borrow().renderer.gpu_specs().into() } + + fn play_system_bell(&self) { + let state = self.borrow(); + let surface = if state.surface_state.toplevel().is_some() { + Some(&state.surface) + } else { + None + }; + if let Some(bell) = state.globals.system_bell.as_ref() { + bell.ring(surface); + } + } } fn update_window(mut state: RefMut) { diff --git a/crates/gpui_linux/src/linux/x11/window.rs b/crates/gpui_linux/src/linux/x11/window.rs index 5e1287976cbb3ba9bc2c1571fa9e215f47fdd615..1974cc0bb28f62da4d7dcb3e9fca92b6324470bb 100644 --- a/crates/gpui_linux/src/linux/x11/window.rs +++ b/crates/gpui_linux/src/linux/x11/window.rs @@ -1846,4 +1846,9 @@ impl PlatformWindow for X11Window { fn gpu_specs(&self) -> Option { self.0.state.borrow().renderer.gpu_specs().into() } + + fn play_system_bell(&self) { + // Volume 0% means don't increase or decrease from system volume + let _ = self.0.xcb.bell(0); + } } diff --git a/crates/gpui_macos/Cargo.toml b/crates/gpui_macos/Cargo.toml index 06e5d0e7321af523a249f19ec0d5ac50e2da5d3f..3626bbd05e8a7c7fa2ae577f11e5277da995d2f7 100644 --- a/crates/gpui_macos/Cargo.toml +++ b/crates/gpui_macos/Cargo.toml @@ -48,6 +48,7 @@ mach2.workspace = true media.workspace = true metal.workspace = true objc.workspace = true +objc2-app-kit.workspace = true parking_lot.workspace = true pathfinder_geometry = "0.5" raw-window-handle = "0.6" diff --git a/crates/gpui_macos/src/window.rs b/crates/gpui_macos/src/window.rs index 398cf46eab09dc8412ffdda8eb550b8ad4e09b40..ace36d695401ce76949129197dcd05135508f7d3 100644 --- a/crates/gpui_macos/src/window.rs +++ b/crates/gpui_macos/src/window.rs @@ -49,6 +49,7 @@ use objc::{ runtime::{BOOL, Class, NO, Object, Protocol, Sel, YES}, sel, sel_impl, }; +use objc2_app_kit::NSBeep; use parking_lot::Mutex; use raw_window_handle as rwh; use smallvec::SmallVec; @@ -1676,6 +1677,10 @@ impl PlatformWindow for MacWindow { } } + fn play_system_bell(&self) { + unsafe { NSBeep() } + } + #[cfg(any(test, feature = "test-support"))] fn render_to_image(&self, scene: &gpui::Scene) -> Result { let mut this = self.0.lock(); diff --git a/crates/gpui_windows/src/window.rs b/crates/gpui_windows/src/window.rs index 3a55100dfb75e961f57b977297bfcd2dc2ae2701..92255f93fd95969931c6b1ae8cb465ff628f82cb 100644 --- a/crates/gpui_windows/src/window.rs +++ b/crates/gpui_windows/src/window.rs @@ -20,7 +20,9 @@ use windows::{ Foundation::*, Graphics::Dwm::*, Graphics::Gdi::*, - System::{Com::*, LibraryLoader::*, Ole::*, SystemServices::*}, + System::{ + Com::*, Diagnostics::Debug::MessageBeep, LibraryLoader::*, Ole::*, SystemServices::*, + }, UI::{Controls::*, HiDpi::*, Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*}, }, core::*, @@ -950,6 +952,11 @@ impl PlatformWindow for WindowsWindow { self.0.update_ime_position(self.0.hwnd, caret_position); } + + fn play_system_bell(&self) { + // MB_OK: The sound specified as the Windows Default Beep sound. + let _ = unsafe { MessageBeep(MB_OK) }; + } } #[implement(IDropTarget)] diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs index 400b2a22bc6071b62c6ce22a2b1bf1053c8cf871..6929ae4e4ca8ca0ee00c9793c948892043dd6dd6 100644 --- a/crates/icons/src/icons.rs +++ b/crates/icons/src/icons.rs @@ -174,6 +174,7 @@ pub enum IconName { LockOutlined, MagnifyingGlass, Maximize, + MaximizeAlt, Menu, MenuAltTemp, Mic, @@ -240,6 +241,7 @@ pub enum IconName { ThinkingModeOff, Thread, ThreadFromSummary, + ThreadImport, ThreadsSidebarLeftClosed, ThreadsSidebarLeftOpen, ThreadsSidebarRightClosed, diff --git a/crates/markdown/src/markdown.rs b/crates/markdown/src/markdown.rs index 7b95688df54610f92b6960d9afc3037bf484b8ed..024e377c2538214c9579c8f025250e2166cf7ace 100644 --- a/crates/markdown/src/markdown.rs +++ b/crates/markdown/src/markdown.rs @@ -1609,23 +1609,18 @@ impl Element for MarkdownElement { builder.table.start(alignments.clone()); let column_count = alignments.len(); - builder.push_div( - div().flex().flex_col().items_start(), - range, - markdown_end, - ); builder.push_div( div() .id(("table", range.start)) - .min_w_0() .grid() .grid_cols(column_count as u16) .when(self.style.table_columns_min_size, |this| { this.grid_cols_min_content(column_count as u16) }) .when(!self.style.table_columns_min_size, |this| { - this.grid_cols_max_content(column_count as u16) + this.grid_cols(column_count as u16) }) + .w_full() .mb_2() .border(px(1.5)) .border_color(cx.theme().colors().border) @@ -1770,7 +1765,6 @@ impl Element for MarkdownElement { } } MarkdownTagEnd::Table => { - builder.pop_div(); builder.pop_div(); builder.table.end(); } diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index 685b1bf8dac8bdf6989aa963c6198753c773f881..8fd18d784dd37d845ffa76c9483b30ae77577a03 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -7144,6 +7144,7 @@ fn worktree_to_proto(worktree: &git::repository::Worktree) -> proto::Worktree { .map(|s| s.to_string()) .unwrap_or_default(), sha: worktree.sha.to_string(), + is_main: worktree.is_main, } } @@ -7152,6 +7153,7 @@ fn proto_to_worktree(proto: &proto::Worktree) -> git::repository::Worktree { path: PathBuf::from(proto.path.clone()), ref_name: Some(SharedString::from(&proto.ref_name)), sha: proto.sha.clone().into(), + is_main: proto.is_main, } } diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 10fb447a6f6c7867212a4622d084deb4fcea91a2..286d3a85f86173bff5d17d8d7c86d26464a04714 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -4413,7 +4413,8 @@ impl LspStore { this.update_local_worktree_language_servers(&worktree, changes, cx); } worktree::Event::UpdatedGitRepositories(_) - | worktree::Event::DeletedEntry(_) => {} + | worktree::Event::DeletedEntry(_) + | worktree::Event::Deleted => {} }) .detach() } diff --git a/crates/project/src/manifest_tree.rs b/crates/project/src/manifest_tree.rs index 82dd1bc0d3fdd0149ced5ce3f2cf9ae480c9f2b7..1ae5b0e809f3803c3f8858afb065637ba0a0f256 100644 --- a/crates/project/src/manifest_tree.rs +++ b/crates/project/src/manifest_tree.rs @@ -59,6 +59,7 @@ impl WorktreeRoots { let path = TriePath::from(entry.path.as_ref()); this.roots.remove(&path); } + WorktreeEvent::Deleted => {} } }), }) diff --git a/crates/project/src/worktree_store.rs b/crates/project/src/worktree_store.rs index 4d464182fa670c6efc7ea2644abd68ef0dcda90a..92f7db453a81c6224455002b7811f2e6945f2a82 100644 --- a/crates/project/src/worktree_store.rs +++ b/crates/project/src/worktree_store.rs @@ -808,6 +808,10 @@ impl WorktreeStore { worktree::Event::DeletedEntry(id) => { cx.emit(WorktreeStoreEvent::WorktreeDeletedEntry(worktree_id, *id)) } + worktree::Event::Deleted => { + // The worktree root itself has been deleted (for single-file worktrees) + // The worktree will be removed via the observe_release callback + } } }) .detach(); diff --git a/crates/proto/proto/git.proto b/crates/proto/proto/git.proto index 0b0e5b86f707a3c7a8edf2a92c182b24235f4eec..18a9afad221485976022c9bf03a57249c7b1d9dd 100644 --- a/crates/proto/proto/git.proto +++ b/crates/proto/proto/git.proto @@ -576,6 +576,7 @@ message Worktree { string path = 1; string ref_name = 2; string sha = 3; + bool is_main = 4; } message GitCreateWorktree { diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 4dc06036ef8416fd859cc815ab090ba5896c0040..22987f6c56669e1972a9bfc940449991d9f55642 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -1264,13 +1264,8 @@ impl PickerDelegate for RecentProjectsDelegate { this.tooltip(Tooltip::text(path.to_string_lossy().to_string())) }), ) - .map(|el| { - if self.selected_index == ix { - el.end_slot(secondary_actions) - } else { - el.end_hover_slot(secondary_actions) - } - }) + .end_slot(secondary_actions) + .show_end_slot_on_hover() .into_any_element(), ) } @@ -1363,13 +1358,8 @@ impl PickerDelegate for RecentProjectsDelegate { }) .tooltip(Tooltip::text(tooltip_path)), ) - .map(|el| { - if self.selected_index == ix { - el.end_slot(secondary_actions) - } else { - el.end_hover_slot(secondary_actions) - } - }) + .end_slot(secondary_actions) + .show_end_slot_on_hover() .into_any_element(), ) } @@ -1503,13 +1493,8 @@ impl PickerDelegate for RecentProjectsDelegate { }) .tooltip(Tooltip::text(tooltip_path)), ) - .map(|el| { - if self.selected_index == ix { - el.end_slot(secondary_actions) - } else { - el.end_hover_slot(secondary_actions) - } - }) + .end_slot(secondary_actions) + .show_end_slot_on_hover() .into_any_element(), ) } diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index f7054687579155d4895ae191de1b7fa7cd14fbf6..26592a8035d50caa4e267a5478d5aceb9fba6e3e 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -1621,23 +1621,24 @@ impl RemoteServerProjects { })) .tooltip(Tooltip::text(project.paths.join("\n"))) .when(is_from_zed, |server_list_item| { - server_list_item.end_hover_slot::(Some( - div() - .mr_2() - .child({ - let project = project.clone(); - // Right-margin to offset it from the Scrollbar - IconButton::new("remove-remote-project", IconName::Trash) - .icon_size(IconSize::Small) - .shape(IconButtonShape::Square) - .size(ButtonSize::Large) - .tooltip(Tooltip::text("Delete Remote Project")) - .on_click(cx.listener(move |this, _, _, cx| { - this.delete_remote_project(server_ix, &project, cx) - })) - }) - .into_any_element(), - )) + server_list_item + .end_slot( + div() + .mr_2() + .child({ + let project = project.clone(); + IconButton::new("remove-remote-project", IconName::Trash) + .icon_size(IconSize::Small) + .shape(IconButtonShape::Square) + .size(ButtonSize::Large) + .tooltip(Tooltip::text("Delete Remote Project")) + .on_click(cx.listener(move |this, _, _, cx| { + this.delete_remote_project(server_ix, &project, cx) + })) + }) + .into_any_element(), + ) + .show_end_slot_on_hover() }), ) } @@ -2413,9 +2414,8 @@ impl RemoteServerProjects { .spacing(ui::ListItemSpacing::Sparse) .start_slot(Icon::new(IconName::Copy).color(Color::Muted)) .child(Label::new("Copy Server Address")) - .end_hover_slot( - Label::new(connection_string.clone()).color(Color::Muted), - ) + .end_slot(Label::new(connection_string.clone()).color(Color::Muted)) + .show_end_slot_on_hover() .on_click({ let connection_string = connection_string.clone(); move |_, _, cx| { diff --git a/crates/rules_library/src/rules_library.rs b/crates/rules_library/src/rules_library.rs index 23e7b83772f755089c49f824719af389ec589bd9..7e5a56f22d48c4d51f60d7d200dc8384582beb23 100644 --- a/crates/rules_library/src/rules_library.rs +++ b/crates/rules_library/src/rules_library.rs @@ -389,7 +389,7 @@ impl PickerDelegate for RulePickerDelegate { })) })) .when(!prompt_id.is_built_in(), |this| { - this.end_hover_slot( + this.end_slot_on_hover( h_flex() .child( IconButton::new("delete-rule", IconName::Trash) diff --git a/crates/sidebar/src/project_group_builder.rs b/crates/sidebar/src/project_group_builder.rs index 318dfac0a839e28ceb27c6036b87e6a13d9bc992..0b8e56ac99565218dd827048afdee71e896f2667 100644 --- a/crates/sidebar/src/project_group_builder.rs +++ b/crates/sidebar/src/project_group_builder.rs @@ -255,6 +255,7 @@ mod tests { path: std::path::PathBuf::from("/wt/feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "abc".into(), + is_main: false, }); }) .expect("git state should be set"); diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index 4adf1dbf95e9c861128ec2fbbc07218f6cc09dce..3883dcd1d089b417207d20db77a3af4e3afad01e 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -3974,10 +3974,24 @@ impl Sidebar { } fn render_sidebar_bottom_bar(&mut self, cx: &mut Context) -> impl IntoElement { - let on_right = self.side(cx) == SidebarSide::Right; let is_archive = matches!(self.view, SidebarView::Archive(..)); + let show_import_button = is_archive && !self.should_render_acp_import_onboarding(cx); + let on_right = self.side(cx) == SidebarSide::Right; + let action_buttons = h_flex() .gap_1() + .when(on_right, |this| this.flex_row_reverse()) + .when(show_import_button, |this| { + this.child( + IconButton::new("thread-import", IconName::ThreadImport) + .icon_size(IconSize::Small) + .tooltip(Tooltip::text("Import ACP Threads")) + .on_click(cx.listener(|this, _, window, cx| { + this.show_archive(window, cx); + this.show_thread_import_modal(window, cx); + })), + ) + }) .child( IconButton::new("archive", IconName::Archive) .icon_size(IconSize::Small) @@ -3990,21 +4004,16 @@ impl Sidebar { })), ) .child(self.render_recent_projects_button(cx)); - let border_color = cx.theme().colors().border; - let toggle_button = self.render_sidebar_toggle_button(cx); - let bar = h_flex() + h_flex() .p_1() .gap_1() + .when(on_right, |this| this.flex_row_reverse()) .justify_between() .border_t_1() - .border_color(border_color); - - if on_right { - bar.child(action_buttons).child(toggle_button) - } else { - bar.child(toggle_button).child(action_buttons) - } + .border_color(cx.theme().colors().border) + .child(self.render_sidebar_toggle_button(cx)) + .child(action_buttons) } fn active_workspace(&self, cx: &App) -> Option> { @@ -4074,7 +4083,7 @@ impl Sidebar { v_flex() .min_w_0() .w_full() - .p_1p5() + .p_2() .border_t_1() .border_color(cx.theme().colors().border) .bg(linear_gradient( @@ -4102,8 +4111,8 @@ impl Sidebar { .style(ButtonStyle::OutlinedCustom(cx.theme().colors().border)) .label_size(LabelSize::Small) .start_icon( - Icon::new(IconName::ArrowDown) - .size(IconSize::XSmall) + Icon::new(IconName::ThreadImport) + .size(IconSize::Small) .color(Color::Muted), ) .on_click(cx.listener(|this, _, window, cx| { @@ -4132,9 +4141,6 @@ impl Sidebar { let Some(agent_panel) = active_workspace.read(cx).panel::(cx) else { return; }; - let Some(agent_registry_store) = AgentRegistryStore::try_global(cx) else { - return; - }; let agent_server_store = active_workspace .read(cx) @@ -4149,9 +4155,6 @@ impl Sidebar { ThreadsArchiveView::new( agent_connection_store.clone(), agent_server_store.clone(), - agent_registry_store.downgrade(), - active_workspace.downgrade(), - self.multi_workspace.clone(), window, cx, ) diff --git a/crates/sidebar/src/sidebar_tests.rs b/crates/sidebar/src/sidebar_tests.rs index dacf9fc40b4ae530d72af3afadffa0f9133952d8..d466e4cfb6585276c49dfcd8b2cc20f5cfe099fd 100644 --- a/crates/sidebar/src/sidebar_tests.rs +++ b/crates/sidebar/src/sidebar_tests.rs @@ -2463,6 +2463,7 @@ async fn test_cmd_n_shows_new_thread_entry_in_absorbed_worktree(cx: &mut TestApp path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -2577,6 +2578,7 @@ async fn test_search_matches_worktree_name(cx: &mut TestAppContext) { path: std::path::PathBuf::from("/wt/rosewood"), ref_name: Some("refs/heads/rosewood".into()), sha: "abc".into(), + is_main: false, }); }) .unwrap(); @@ -2638,6 +2640,7 @@ async fn test_git_worktree_added_live_updates_sidebar(cx: &mut TestAppContext) { path: std::path::PathBuf::from("/wt/rosewood"), ref_name: Some("refs/heads/rosewood".into()), sha: "abc".into(), + is_main: false, }); }) .unwrap(); @@ -2739,11 +2742,13 @@ async fn test_two_worktree_workspaces_absorbed_when_main_added(cx: &mut TestAppC path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); state.worktrees.push(git::repository::Worktree { path: std::path::PathBuf::from("/wt-feature-b"), ref_name: Some("refs/heads/feature-b".into()), sha: "bbb".into(), + is_main: false, }); }) .unwrap(); @@ -2821,11 +2826,13 @@ async fn test_threadless_workspace_shows_new_thread_with_worktree_chip(cx: &mut path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); state.worktrees.push(git::repository::Worktree { path: std::path::PathBuf::from("/wt-feature-b"), ref_name: Some("refs/heads/feature-b".into()), sha: "bbb".into(), + is_main: false, }); }) .unwrap(); @@ -2941,6 +2948,7 @@ async fn test_multi_worktree_thread_shows_multiple_chips(cx: &mut TestAppContext path: std::path::PathBuf::from(format!("/worktrees/{repo}/{branch}/{repo}")), ref_name: Some(format!("refs/heads/{branch}").into()), sha: "aaa".into(), + is_main: false, }); } }) @@ -3043,6 +3051,7 @@ async fn test_same_named_worktree_chips_are_deduplicated(cx: &mut TestAppContext path: std::path::PathBuf::from(format!("/worktrees/{repo}/olivetti/{repo}")), ref_name: Some("refs/heads/olivetti".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -3133,6 +3142,7 @@ async fn test_absorbed_worktree_running_thread_shows_live_status(cx: &mut TestAp path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -3248,6 +3258,7 @@ async fn test_absorbed_worktree_completion_triggers_notification(cx: &mut TestAp path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -3354,6 +3365,7 @@ async fn test_clicking_worktree_thread_opens_workspace_when_none_exists(cx: &mut path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -3459,6 +3471,7 @@ async fn test_clicking_worktree_thread_does_not_briefly_render_as_separate_proje path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -3609,6 +3622,7 @@ async fn test_clicking_absorbed_worktree_thread_activates_worktree_workspace( path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -4203,6 +4217,7 @@ async fn test_archive_thread_uses_next_threads_own_workspace(cx: &mut TestAppCon path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -4374,6 +4389,7 @@ async fn test_linked_worktree_threads_not_duplicated_across_groups(cx: &mut Test path: std::path::PathBuf::from("/wt-feature-a"), ref_name: Some("refs/heads/feature-a".into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); @@ -5362,6 +5378,7 @@ mod property_test { path: worktree_pathbuf, ref_name: Some(format!("refs/heads/{}", worktree_name).into()), sha: "aaa".into(), + is_main: false, }); }) .unwrap(); diff --git a/crates/tab_switcher/src/tab_switcher.rs b/crates/tab_switcher/src/tab_switcher.rs index 0fb13c85d21797e4d57728c88fc8bb014a898f78..d1e19ea4faee8d8259d06e2c24875faac7a0117c 100644 --- a/crates/tab_switcher/src/tab_switcher.rs +++ b/crates/tab_switcher/src/tab_switcher.rs @@ -875,7 +875,7 @@ impl PickerDelegate for TabSwitcherDelegate { el.end_slot::(close_button) } else { el.end_slot::(indicator) - .end_hover_slot::(close_button) + .end_slot_on_hover::(close_button) } }), ) diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 34f0cd809692d649bcfbabb7952f3075618ead04..285a07c9562849b26b4cbba3de3979614384d875 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -570,7 +570,7 @@ impl PickerDelegate for TasksModalDelegate { Tooltip::simple("Delete Previously Scheduled Task", cx) }), ); - item.end_hover_slot(delete_button) + item.end_slot_on_hover(delete_button) } else { item } diff --git a/crates/title_bar/src/onboarding_banner.rs b/crates/title_bar/src/onboarding_banner.rs index f96ce3a92740da4a0aac3dc154384f20f3b05eb0..96400a91a0a26fdc6a4c1acb6387f27c3077e393 100644 --- a/crates/title_bar/src/onboarding_banner.rs +++ b/crates/title_bar/src/onboarding_banner.rs @@ -1,3 +1,7 @@ +// This module provides infrastructure for showing onboarding banners in the title bar. +// It's currently not in use but is kept for future feature announcements. +#![allow(dead_code)] + use gpui::{Action, Entity, Global, Render, SharedString}; use ui::{ButtonLike, Tooltip, prelude::*}; use util::ResultExt; @@ -94,21 +98,21 @@ fn persist_dismissed(source: &str, cx: &mut App) { } pub fn restore_banner(cx: &mut App) { - cx.defer(|cx| { - cx.global::() - .entity - .clone() - .update(cx, |this, cx| { + if let Some(banner_global) = cx.try_global::() { + let entity = banner_global.entity.clone(); + cx.defer(move |cx| { + entity.update(cx, |this, cx| { this.dismissed = false; cx.notify(); }); - }); + }); - let source = &cx.global::().entity.read(cx).source; - let dismissed_at = dismissed_at_key(source); - let kvp = db::kvp::KeyValueStore::global(cx); - cx.spawn(async move |_| kvp.delete_kvp(dismissed_at).await) - .detach_and_log_err(cx); + let source = &cx.global::().entity.read(cx).source; + let dismissed_at = dismissed_at_key(source); + let kvp = db::kvp::KeyValueStore::global(cx); + cx.spawn(async move |_| kvp.delete_kvp(dismissed_at).await) + .detach_and_log_err(cx); + } } impl Render for OnboardingBanner { diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index 42c348bacb680e2a09586d0dc0279fc8c95d1604..440249907adb6d29602ad8e950d0fd26a2d1c31d 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -155,7 +155,7 @@ pub struct TitleBar { multi_workspace: Option>, application_menu: Option>, _subscriptions: Vec, - banner: Entity, + banner: Option>, update_version: Entity, screen_share_popover_handle: PopoverMenuHandle, _diagnostics_subscription: Option, @@ -246,7 +246,9 @@ impl Render for TitleBar { children.push(self.render_collaborator_list(window, cx).into_any_element()); if title_bar_settings.show_onboarding_banner { - children.push(self.banner.clone().into_any_element()) + if let Some(banner) = &self.banner { + children.push(banner.clone().into_any_element()) + } } let status = self.client.status(); @@ -385,19 +387,6 @@ impl TitleBar { })); } - let banner = cx.new(|cx| { - OnboardingBanner::new( - "ACP Claude Code Onboarding", - IconName::AiClaude, - "Claude Agent", - Some("Introducing:".into()), - zed_actions::agent::OpenClaudeAgentOnboardingModal.boxed_clone(), - cx, - ) - // When updating this to a non-AI feature release, remove this line. - .visible_when(|cx| !project::DisableAiSettings::get_global(cx).disable_ai) - }); - let update_version = cx.new(|cx| UpdateVersion::new(cx)); let platform_titlebar = cx.new(|cx| { let mut titlebar = PlatformTitleBar::new(id, cx); @@ -416,7 +405,7 @@ impl TitleBar { user_store, client, _subscriptions: subscriptions, - banner, + banner: None, update_version, screen_share_popover_handle: PopoverMenuHandle::default(), _diagnostics_subscription: None, diff --git a/crates/ui/src/components/list/list_item.rs b/crates/ui/src/components/list/list_item.rs index 693cf3d52e34369d04db445d1ddac765691fb429..9a764efd58cfd3365d92e534a715a0f23ce46e90 100644 --- a/crates/ui/src/components/list/list_item.rs +++ b/crates/ui/src/components/list/list_item.rs @@ -14,6 +14,14 @@ pub enum ListItemSpacing { Sparse, } +#[derive(Default)] +enum EndSlotVisibility { + #[default] + Always, + OnHover, + SwapOnHover(AnyElement), +} + #[derive(IntoElement, RegisterComponent)] pub struct ListItem { id: ElementId, @@ -28,9 +36,7 @@ pub struct ListItem { /// A slot for content that appears after the children, usually on the other side of the header. /// This might be a button, a disclosure arrow, a face pile, etc. end_slot: Option, - /// A slot for content that appears on hover after the children - /// It will obscure the `end_slot` when visible. - end_hover_slot: Option, + end_slot_visibility: EndSlotVisibility, toggle: Option, inset: bool, on_click: Option>, @@ -61,7 +67,7 @@ impl ListItem { indent_step_size: px(12.), start_slot: None, end_slot: None, - end_hover_slot: None, + end_slot_visibility: EndSlotVisibility::default(), toggle: None, inset: false, on_click: None, @@ -165,8 +171,14 @@ impl ListItem { self } - pub fn end_hover_slot(mut self, end_hover_slot: impl Into>) -> Self { - self.end_hover_slot = end_hover_slot.into().map(IntoElement::into_any_element); + pub fn end_slot_on_hover(mut self, end_slot_on_hover: E) -> Self { + self.end_slot_visibility = + EndSlotVisibility::SwapOnHover(end_slot_on_hover.into_any_element()); + self + } + + pub fn show_end_slot_on_hover(mut self) -> Self { + self.end_slot_visibility = EndSlotVisibility::OnHover; self } @@ -234,9 +246,9 @@ impl RenderOnce for ListItem { this.ml(self.indent_level as f32 * self.indent_step_size) .px(DynamicSpacing::Base04.rems(cx)) }) - .when(!self.inset && !self.disabled, |this| { + .when(!self.inset, |this| { this.when_some(self.focused, |this, focused| { - if focused { + if focused && !self.disabled { this.border_1() .when(self.docked_right, |this| this.border_r_2()) .border_color(cx.theme().colors().border_focused) @@ -244,7 +256,7 @@ impl RenderOnce for ListItem { this.border_1() } }) - .when(self.selectable, |this| { + .when(self.selectable && !self.disabled, |this| { this.hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) .active(|style| style.bg(cx.theme().colors().ghost_element_active)) .when(self.outlined, |this| this.rounded_sm()) @@ -268,16 +280,16 @@ impl RenderOnce for ListItem { ListItemSpacing::ExtraDense => this.py_neg_px(), ListItemSpacing::Sparse => this.py_1(), }) - .when(self.inset && !self.disabled, |this| { + .when(self.inset, |this| { this.when_some(self.focused, |this, focused| { - if focused { + if focused && !self.disabled { this.border_1() .border_color(cx.theme().colors().border_focused) } else { this.border_1() } }) - .when(self.selectable, |this| { + .when(self.selectable && !self.disabled, |this| { this.hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) .active(|style| style.bg(cx.theme().colors().ghost_element_active)) .when(self.selected, |this| { @@ -338,28 +350,31 @@ impl RenderOnce for ListItem { .children(self.start_slot) .children(self.children), ) + .when(self.end_slot.is_some(), |this| this.justify_between()) .when_some(self.end_slot, |this, end_slot| { - this.justify_between().child( - h_flex() + this.child(match self.end_slot_visibility { + EndSlotVisibility::Always => { + h_flex().flex_shrink().overflow_hidden().child(end_slot) + } + EndSlotVisibility::OnHover => h_flex() .flex_shrink() .overflow_hidden() - .when(self.end_hover_slot.is_some(), |this| { - this.visible() - .group_hover("list_item", |this| this.invisible()) - }) - .child(end_slot), - ) - }) - .when_some(self.end_hover_slot, |this, end_hover_slot| { - this.child( - h_flex() - .h_full() - .absolute() - .right(DynamicSpacing::Base06.rems(cx)) - .top_0() .visible_on_hover("list_item") - .child(end_hover_slot), - ) + .child(end_slot), + EndSlotVisibility::SwapOnHover(hover_slot) => h_flex() + .relative() + .flex_shrink() + .child(h_flex().visible_on_hover("list_item").child(hover_slot)) + .child( + h_flex() + .absolute() + .inset_0() + .justify_end() + .overflow_hidden() + .group_hover("list_item", |this| this.invisible()) + .child(end_slot), + ), + }) }), ) } diff --git a/crates/workspace/src/multi_workspace.rs b/crates/workspace/src/multi_workspace.rs index 862f7c7b267721833fa395e501b604d30745a1b7..10a5ce70ead2d5aea7cc21a9af53ee9f216859c3 100644 --- a/crates/workspace/src/multi_workspace.rs +++ b/crates/workspace/src/multi_workspace.rs @@ -1113,178 +1113,3 @@ impl Render for MultiWorkspace { ) } } - -#[cfg(test)] -mod tests { - use super::*; - use fs::FakeFs; - use gpui::TestAppContext; - use settings::SettingsStore; - - fn init_test(cx: &mut TestAppContext) { - cx.update(|cx| { - let settings_store = SettingsStore::test(cx); - cx.set_global(settings_store); - theme_settings::init(theme::LoadThemes::JustBase, cx); - DisableAiSettings::register(cx); - cx.update_flags(false, vec!["agent-v2".into()]); - }); - } - - #[gpui::test] - async fn test_sidebar_disabled_when_disable_ai_is_enabled(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.executor()); - let project = Project::test(fs, [], cx).await; - - let (multi_workspace, cx) = - cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx)); - - multi_workspace.read_with(cx, |mw, cx| { - assert!(mw.multi_workspace_enabled(cx)); - }); - - multi_workspace.update_in(cx, |mw, _window, cx| { - mw.open_sidebar(cx); - assert!(mw.sidebar_open()); - }); - - cx.update(|_window, cx| { - DisableAiSettings::override_global(DisableAiSettings { disable_ai: true }, cx); - }); - cx.run_until_parked(); - - multi_workspace.read_with(cx, |mw, cx| { - assert!( - !mw.sidebar_open(), - "Sidebar should be closed when disable_ai is true" - ); - assert!( - !mw.multi_workspace_enabled(cx), - "Multi-workspace should be disabled when disable_ai is true" - ); - }); - - multi_workspace.update_in(cx, |mw, window, cx| { - mw.toggle_sidebar(window, cx); - }); - multi_workspace.read_with(cx, |mw, _cx| { - assert!( - !mw.sidebar_open(), - "Sidebar should remain closed when toggled with disable_ai true" - ); - }); - - cx.update(|_window, cx| { - DisableAiSettings::override_global(DisableAiSettings { disable_ai: false }, cx); - }); - cx.run_until_parked(); - - multi_workspace.read_with(cx, |mw, cx| { - assert!( - mw.multi_workspace_enabled(cx), - "Multi-workspace should be enabled after re-enabling AI" - ); - assert!( - !mw.sidebar_open(), - "Sidebar should still be closed after re-enabling AI (not auto-opened)" - ); - }); - - multi_workspace.update_in(cx, |mw, window, cx| { - mw.toggle_sidebar(window, cx); - }); - multi_workspace.read_with(cx, |mw, _cx| { - assert!( - mw.sidebar_open(), - "Sidebar should open when toggled after re-enabling AI" - ); - }); - } - - #[gpui::test] - async fn test_replace(cx: &mut TestAppContext) { - init_test(cx); - let fs = FakeFs::new(cx.executor()); - let project_a = Project::test(fs.clone(), [], cx).await; - let project_b = Project::test(fs.clone(), [], cx).await; - let project_c = Project::test(fs.clone(), [], cx).await; - let project_d = Project::test(fs.clone(), [], cx).await; - - let (multi_workspace, cx) = cx - .add_window_view(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx)); - - let workspace_a_id = - multi_workspace.read_with(cx, |mw, _cx| mw.workspaces()[0].entity_id()); - - // Replace the only workspace (single-workspace case). - let workspace_b = multi_workspace.update_in(cx, |mw, window, cx| { - let workspace = cx.new(|cx| Workspace::test_new(project_b.clone(), window, cx)); - mw.replace(workspace.clone(), &*window, cx); - workspace - }); - - multi_workspace.read_with(cx, |mw, _cx| { - assert_eq!(mw.workspaces().len(), 1); - assert_eq!( - mw.workspaces()[0].entity_id(), - workspace_b.entity_id(), - "slot should now be project_b" - ); - assert_ne!( - mw.workspaces()[0].entity_id(), - workspace_a_id, - "project_a should be gone" - ); - }); - - // Add project_c as a second workspace, then replace it with project_d. - let workspace_c = multi_workspace.update_in(cx, |mw, window, cx| { - mw.test_add_workspace(project_c.clone(), window, cx) - }); - - multi_workspace.read_with(cx, |mw, _cx| { - assert_eq!(mw.workspaces().len(), 2); - assert_eq!(mw.active_workspace_index(), 1); - }); - - let workspace_d = multi_workspace.update_in(cx, |mw, window, cx| { - let workspace = cx.new(|cx| Workspace::test_new(project_d.clone(), window, cx)); - mw.replace(workspace.clone(), &*window, cx); - workspace - }); - - multi_workspace.read_with(cx, |mw, _cx| { - assert_eq!(mw.workspaces().len(), 2, "should still have 2 workspaces"); - assert_eq!(mw.active_workspace_index(), 1); - assert_eq!( - mw.workspaces()[1].entity_id(), - workspace_d.entity_id(), - "active slot should now be project_d" - ); - assert_ne!( - mw.workspaces()[1].entity_id(), - workspace_c.entity_id(), - "project_c should be gone" - ); - }); - - // Replace with workspace_b which is already in the list — should just switch. - multi_workspace.update_in(cx, |mw, window, cx| { - mw.replace(workspace_b.clone(), &*window, cx); - }); - - multi_workspace.read_with(cx, |mw, _cx| { - assert_eq!( - mw.workspaces().len(), - 2, - "no workspace should be added or removed" - ); - assert_eq!( - mw.active_workspace_index(), - 0, - "should have switched to workspace_b" - ); - }); - } -} diff --git a/crates/workspace/src/multi_workspace_tests.rs b/crates/workspace/src/multi_workspace_tests.rs new file mode 100644 index 0000000000000000000000000000000000000000..50161121719ec7b2835fd11e389f24860e57d8f5 --- /dev/null +++ b/crates/workspace/src/multi_workspace_tests.rs @@ -0,0 +1,172 @@ +use super::*; +use feature_flags::FeatureFlagAppExt; +use fs::FakeFs; +use gpui::TestAppContext; +use project::DisableAiSettings; +use settings::SettingsStore; + +fn init_test(cx: &mut TestAppContext) { + cx.update(|cx| { + let settings_store = SettingsStore::test(cx); + cx.set_global(settings_store); + theme_settings::init(theme::LoadThemes::JustBase, cx); + DisableAiSettings::register(cx); + cx.update_flags(false, vec!["agent-v2".into()]); + }); +} + +#[gpui::test] +async fn test_sidebar_disabled_when_disable_ai_is_enabled(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + let project = Project::test(fs, [], cx).await; + + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx)); + + multi_workspace.read_with(cx, |mw, cx| { + assert!(mw.multi_workspace_enabled(cx)); + }); + + multi_workspace.update_in(cx, |mw, _window, cx| { + mw.open_sidebar(cx); + assert!(mw.sidebar_open()); + }); + + cx.update(|_window, cx| { + DisableAiSettings::override_global(DisableAiSettings { disable_ai: true }, cx); + }); + cx.run_until_parked(); + + multi_workspace.read_with(cx, |mw, cx| { + assert!( + !mw.sidebar_open(), + "Sidebar should be closed when disable_ai is true" + ); + assert!( + !mw.multi_workspace_enabled(cx), + "Multi-workspace should be disabled when disable_ai is true" + ); + }); + + multi_workspace.update_in(cx, |mw, window, cx| { + mw.toggle_sidebar(window, cx); + }); + multi_workspace.read_with(cx, |mw, _cx| { + assert!( + !mw.sidebar_open(), + "Sidebar should remain closed when toggled with disable_ai true" + ); + }); + + cx.update(|_window, cx| { + DisableAiSettings::override_global(DisableAiSettings { disable_ai: false }, cx); + }); + cx.run_until_parked(); + + multi_workspace.read_with(cx, |mw, cx| { + assert!( + mw.multi_workspace_enabled(cx), + "Multi-workspace should be enabled after re-enabling AI" + ); + assert!( + !mw.sidebar_open(), + "Sidebar should still be closed after re-enabling AI (not auto-opened)" + ); + }); + + multi_workspace.update_in(cx, |mw, window, cx| { + mw.toggle_sidebar(window, cx); + }); + multi_workspace.read_with(cx, |mw, _cx| { + assert!( + mw.sidebar_open(), + "Sidebar should open when toggled after re-enabling AI" + ); + }); +} + +#[gpui::test] +async fn test_replace(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.executor()); + let project_a = Project::test(fs.clone(), [], cx).await; + let project_b = Project::test(fs.clone(), [], cx).await; + let project_c = Project::test(fs.clone(), [], cx).await; + let project_d = Project::test(fs.clone(), [], cx).await; + + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx)); + + let workspace_a_id = multi_workspace.read_with(cx, |mw, _cx| mw.workspaces()[0].entity_id()); + + // Replace the only workspace (single-workspace case). + let workspace_b = multi_workspace.update_in(cx, |mw, window, cx| { + let workspace = cx.new(|cx| Workspace::test_new(project_b.clone(), window, cx)); + mw.replace(workspace.clone(), &*window, cx); + workspace + }); + + multi_workspace.read_with(cx, |mw, _cx| { + assert_eq!(mw.workspaces().len(), 1); + assert_eq!( + mw.workspaces()[0].entity_id(), + workspace_b.entity_id(), + "slot should now be project_b" + ); + assert_ne!( + mw.workspaces()[0].entity_id(), + workspace_a_id, + "project_a should be gone" + ); + }); + + // Add project_c as a second workspace, then replace it with project_d. + let workspace_c = multi_workspace.update_in(cx, |mw, window, cx| { + mw.test_add_workspace(project_c.clone(), window, cx) + }); + + multi_workspace.read_with(cx, |mw, _cx| { + assert_eq!(mw.workspaces().len(), 2); + assert_eq!(mw.active_workspace_index(), 1); + }); + + let workspace_d = multi_workspace.update_in(cx, |mw, window, cx| { + let workspace = cx.new(|cx| Workspace::test_new(project_d.clone(), window, cx)); + mw.replace(workspace.clone(), &*window, cx); + workspace + }); + + multi_workspace.read_with(cx, |mw, _cx| { + assert_eq!(mw.workspaces().len(), 2, "should still have 2 workspaces"); + assert_eq!(mw.active_workspace_index(), 1); + assert_eq!( + mw.workspaces()[1].entity_id(), + workspace_d.entity_id(), + "active slot should now be project_d" + ); + assert_ne!( + mw.workspaces()[1].entity_id(), + workspace_c.entity_id(), + "project_c should be gone" + ); + }); + + // Replace with workspace_b which is already in the list — should just switch. + multi_workspace.update_in(cx, |mw, window, cx| { + mw.replace(workspace_b.clone(), &*window, cx); + }); + + multi_workspace.read_with(cx, |mw, _cx| { + assert_eq!( + mw.workspaces().len(), + 2, + "no workspace should be added or removed" + ); + assert_eq!( + mw.active_workspace_index(), + 0, + "should have switched to workspace_b" + ); + }); +} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 33d1befe38e3b48a39377547bd433398b37d6a77..ae05c2c59012b2caf217ac54a80b377aee87f09d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -5,6 +5,8 @@ pub mod invalid_item_view; pub mod item; mod modal_layer; mod multi_workspace; +#[cfg(test)] +mod multi_workspace_tests; pub mod notifications; pub mod pane; pub mod pane_group; diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index 1ad3e7ecaa694326aa479b5b69ccd3206fbf1e8d..b08f9aaee016d7047b06bf9ac4a4a1ce2b2d1ad8 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -355,6 +355,7 @@ enum ScanState { RootUpdated { new_path: Arc, }, + RootDeleted, } struct UpdateObservationState { @@ -368,6 +369,8 @@ pub enum Event { UpdatedEntries(UpdatedEntriesSet), UpdatedGitRepositories(UpdatedGitRepositoriesSet), DeletedEntry(ProjectEntryId), + /// The worktree root itself has been deleted (for single-file worktrees) + Deleted, } impl EventEmitter for Worktree {} @@ -1106,6 +1109,7 @@ impl LocalWorktree { }; let fs_case_sensitive = fs.is_case_sensitive().await; + let is_single_file = snapshot.snapshot.root_dir().is_none(); let mut scanner = BackgroundScanner { fs, fs_case_sensitive, @@ -1128,6 +1132,7 @@ impl LocalWorktree { share_private_files, settings, watcher, + is_single_file, }; scanner @@ -1156,6 +1161,13 @@ impl LocalWorktree { ScanState::RootUpdated { new_path } => { this.update_abs_path_and_refresh(new_path, cx); } + ScanState::RootDeleted => { + log::info!( + "worktree root {} no longer exists, closing worktree", + this.abs_path().display() + ); + cx.emit(Event::Deleted); + } } }); } @@ -2764,7 +2776,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) || entry.is_always_included { + if !entry.is_ignored || entry.is_always_included { assert_eq!(visible_files.next().unwrap().inode, entry.inode); } } @@ -3443,8 +3455,7 @@ pub struct Entry { /// symlink. /// /// We only scan entries outside of the worktree once the symlinked - /// directory is expanded. External entries are treated like gitignored - /// entries in that they are not included in searches. + /// directory is expanded. pub is_external: bool, /// Whether this entry is considered to be a `.env` file. @@ -3649,8 +3660,7 @@ 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) && !self.is_always_included - { + let non_ignored_count = if self.is_ignored && !self.is_always_included { 0 } else { 1 @@ -3799,6 +3809,9 @@ struct BackgroundScanner { watcher: Arc, settings: WorktreeSettings, share_private_files: bool, + /// Whether this is a single-file worktree (root is a file, not a directory). + /// Used to determine if we should give up after repeated canonicalization failures. + is_single_file: bool, } #[derive(Copy, Clone, PartialEq)] @@ -4096,6 +4109,18 @@ impl BackgroundScanner { .ok(); } else { log::error!("root path could not be canonicalized: {err:#}"); + + // For single-file worktrees, if we can't canonicalize and the file handle + // fallback also failed, the file is gone - close the worktree + if self.is_single_file { + log::info!( + "single-file worktree root {:?} no longer exists, marking as deleted", + root_path.as_path() + ); + self.status_updates_tx + .unbounded_send(ScanState::RootDeleted) + .ok(); + } } return; } diff --git a/crates/worktree/tests/integration/main.rs b/crates/worktree/tests/integration/main.rs index fb8ec444dd324e935aad873bf201d4f0b8ae2019..cd7dd1c9056a7d501bec2bcd7b07d596f689a908 100644 --- a/crates/worktree/tests/integration/main.rs +++ b/crates/worktree/tests/integration/main.rs @@ -14,10 +14,12 @@ use worktree::{Entry, EntryKind, Event, PathChange, Worktree, WorktreeModelHandl use serde_json::json; use settings::{SettingsStore, WorktreeId}; use std::{ + cell::Cell, env, fmt::Write, mem, path::{Path, PathBuf}, + rc::Rc, sync::Arc, }; use util::{ @@ -3105,3 +3107,67 @@ async fn test_refresh_entries_for_paths_creates_ancestors(cx: &mut TestAppContex ); }); } + +#[gpui::test] +async fn test_single_file_worktree_deleted(cx: &mut TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background_executor.clone()); + + fs.insert_tree( + "/root", + json!({ + "test.txt": "content", + }), + ) + .await; + + let tree = Worktree::local( + Path::new("/root/test.txt"), + true, + fs.clone(), + Default::default(), + true, + WorktreeId::from_proto(0), + &mut cx.to_async(), + ) + .await + .unwrap(); + + cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete()) + .await; + + tree.read_with(cx, |tree, _| { + assert!(tree.is_single_file(), "Should be a single-file worktree"); + assert_eq!(tree.abs_path().as_ref(), Path::new("/root/test.txt")); + }); + + // Delete the file + fs.remove_file(Path::new("/root/test.txt"), Default::default()) + .await + .unwrap(); + + // Subscribe to worktree events + let deleted_event_received = Rc::new(Cell::new(false)); + let _subscription = cx.update({ + let deleted_event_received = deleted_event_received.clone(); + |cx| { + cx.subscribe(&tree, move |_, event, _| { + if matches!(event, Event::Deleted) { + deleted_event_received.set(true); + } + }) + } + }); + + // Trigger filesystem events - the scanner should detect the file is gone immediately + // and emit a Deleted event + cx.background_executor.run_until_parked(); + cx.background_executor + .advance_clock(std::time::Duration::from_secs(1)); + cx.background_executor.run_until_parked(); + + assert!( + deleted_event_received.get(), + "Should receive Deleted event when single-file worktree root is deleted" + ); +} diff --git a/crates/zed_actions/src/lib.rs b/crates/zed_actions/src/lib.rs index 75b21c528a1e6952700264a154ab4c15045149b0..66ccf9c41c1e1cfcb821e03b4e9b7d4803f53c0b 100644 --- a/crates/zed_actions/src/lib.rs +++ b/crates/zed_actions/src/lib.rs @@ -450,8 +450,6 @@ pub mod agent { OpenOnboardingModal, /// Opens the ACP onboarding modal. OpenAcpOnboardingModal, - /// Opens the Claude Agent onboarding modal. - OpenClaudeAgentOnboardingModal, /// Resets the agent onboarding state. ResetOnboarding, /// Starts a chat conversation with the agent.