From 570abe0590cf6440830816305490ca22e82c3f2f Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 3 Jan 2024 20:02:23 -0500 Subject: [PATCH 01/67] Rename notification_store --- crates/notifications/Cargo.toml | 2 +- .../src/{notification_store2.rs => notification_store.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename crates/notifications/src/{notification_store2.rs => notification_store.rs} (100%) diff --git a/crates/notifications/Cargo.toml b/crates/notifications/Cargo.toml index b04f30835c6750a575b8a6bb874140c3dea913f7..1425e079d6fcbb53cf45ab57850fefe19a495741 100644 --- a/crates/notifications/Cargo.toml +++ b/crates/notifications/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" publish = false [lib] -path = "src/notification_store2.rs" +path = "src/notification_store.rs" doctest = false [features] diff --git a/crates/notifications/src/notification_store2.rs b/crates/notifications/src/notification_store.rs similarity index 100% rename from crates/notifications/src/notification_store2.rs rename to crates/notifications/src/notification_store.rs From 3b90c778b999eaab7b635c92d031626ca0b67270 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 3 Jan 2024 15:52:45 -0800 Subject: [PATCH 02/67] Change binary name --- crates/zed/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index dba4f61793099d05b0163bca52a6404935082527..39ab5e285b77672e31d8b33555f83bd768e4be68 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -11,7 +11,7 @@ path = "src/zed.rs" doctest = false [[bin]] -name = "zed" +name = "Zed" path = "src/main.rs" [dependencies] From 073128e4f3ff1bed653bb343e92d193b68bdb610 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 3 Jan 2024 17:58:06 -0800 Subject: [PATCH 03/67] Remove wasmtime for now --- Cargo.lock | 414 +------------------------------- Cargo.toml | 5 +- crates/language/src/language.rs | 37 +-- 3 files changed, 14 insertions(+), 442 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e09ab0fa57970268c1660e0b509baf21ed2ebd01..1d0af3c00876104d2f8554ee43ed757689bce281 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -268,12 +268,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" - [[package]] name = "arrayref" version = "0.3.7" @@ -1832,105 +1826,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cranelift-bforest" -version = "0.103.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "cranelift-entity", -] - -[[package]] -name = "cranelift-codegen" -version = "0.103.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "bumpalo", - "cranelift-bforest", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-control", - "cranelift-entity", - "cranelift-isle", - "gimli", - "hashbrown 0.14.0", - "log", - "regalloc2", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.103.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "cranelift-codegen-shared", -] - -[[package]] -name = "cranelift-codegen-shared" -version = "0.103.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" - -[[package]] -name = "cranelift-control" -version = "0.103.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "arbitrary", -] - -[[package]] -name = "cranelift-entity" -version = "0.103.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "serde", - "serde_derive", -] - -[[package]] -name = "cranelift-frontend" -version = "0.103.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "cranelift-codegen", - "log", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cranelift-isle" -version = "0.103.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" - -[[package]] -name = "cranelift-native" -version = "0.103.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "cranelift-codegen", - "libc", - "target-lexicon", -] - -[[package]] -name = "cranelift-wasm" -version = "0.103.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", - "itertools 0.10.5", - "log", - "smallvec", - "wasmparser", - "wasmtime-types", -] - [[package]] name = "crc" version = "3.0.1" @@ -2526,12 +2421,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - [[package]] name = "fallible-streaming-iterator" version = "0.1.9" @@ -3042,11 +2931,6 @@ name = "gimli" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" -dependencies = [ - "fallible-iterator 0.3.0", - "indexmap 2.0.0", - "stable_deref_trait", -] [[package]] name = "git" @@ -3559,7 +3443,6 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", "hashbrown 0.14.0", - "serde", ] [[package]] @@ -3925,12 +3808,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - [[package]] name = "libc" version = "0.2.148" @@ -4162,15 +4039,6 @@ dependencies = [ "url", ] -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - [[package]] name = "mach2" version = "0.4.1" @@ -4249,15 +4117,6 @@ version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" -[[package]] -name = "memfd" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" -dependencies = [ - "rustix 0.38.21", -] - [[package]] name = "memmap2" version = "0.2.3" @@ -4913,9 +4772,6 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ - "crc32fast", - "hashbrown 0.14.0", - "indexmap 2.0.0", "memchr", ] @@ -5770,15 +5626,6 @@ version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" -[[package]] -name = "psm" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" -dependencies = [ - "cc", -] - [[package]] name = "ptr_meta" version = "0.1.4" @@ -6052,19 +5899,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "regalloc2" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" -dependencies = [ - "hashbrown 0.13.2", - "log", - "rustc-hash", - "slice-group-by", - "smallvec", -] - [[package]] name = "regex" version = "1.9.5" @@ -6384,7 +6218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" dependencies = [ "bitflags 2.4.1", - "fallible-iterator 0.2.0", + "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", @@ -7209,12 +7043,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "slice-group-by" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" - [[package]] name = "slotmap" version = "1.0.6" @@ -7327,12 +7155,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be6c3f39c37a4283ee4b43d1311c828f2e1fb0541e76ea0cb1a2abd9ef2f5b3b" -[[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" @@ -7588,12 +7410,6 @@ dependencies = [ "uuid 1.4.1", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" @@ -7865,12 +7681,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "target-lexicon" -version = "0.12.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" - [[package]] name = "tempdir" version = "0.3.7" @@ -8524,8 +8334,6 @@ source = "git+https://github.com/tree-sitter/tree-sitter?rev=31c40449749c4263a91 dependencies = [ "cc", "regex", - "wasmtime", - "wasmtime-c-api-impl", ] [[package]] @@ -9290,224 +9098,6 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" -[[package]] -name = "wasm-encoder" -version = "0.38.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad2b51884de9c7f4fe2fd1043fccb8dcad4b1e29558146ee57a144d15779f3f" -dependencies = [ - "leb128", -] - -[[package]] -name = "wasmparser" -version = "0.118.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ee9723b928e735d53000dec9eae7b07a60e490c85ab54abb66659fc61bfcd9" -dependencies = [ - "indexmap 2.0.0", - "semver", -] - -[[package]] -name = "wasmtime" -version = "16.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "anyhow", - "bincode", - "bumpalo", - "cfg-if 1.0.0", - "indexmap 2.0.0", - "libc", - "log", - "object", - "once_cell", - "paste", - "serde", - "serde_derive", - "serde_json", - "target-lexicon", - "wasmparser", - "wasmtime-cranelift", - "wasmtime-environ", - "wasmtime-jit", - "wasmtime-runtime", - "windows-sys 0.48.0", -] - -[[package]] -name = "wasmtime-asm-macros" -version = "16.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "wasmtime-c-api-impl" -version = "16.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "anyhow", - "log", - "once_cell", - "tracing", - "wasmtime", - "wasmtime-c-api-macros", -] - -[[package]] -name = "wasmtime-c-api-macros" -version = "0.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "wasmtime-cranelift" -version = "16.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "anyhow", - "cfg-if 1.0.0", - "cranelift-codegen", - "cranelift-control", - "cranelift-entity", - "cranelift-frontend", - "cranelift-native", - "cranelift-wasm", - "gimli", - "log", - "object", - "target-lexicon", - "thiserror", - "wasmparser", - "wasmtime-cranelift-shared", - "wasmtime-environ", - "wasmtime-versioned-export-macros", -] - -[[package]] -name = "wasmtime-cranelift-shared" -version = "16.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "anyhow", - "cranelift-codegen", - "cranelift-control", - "cranelift-native", - "gimli", - "object", - "target-lexicon", - "wasmtime-environ", -] - -[[package]] -name = "wasmtime-environ" -version = "16.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "anyhow", - "cranelift-entity", - "gimli", - "indexmap 2.0.0", - "log", - "object", - "serde", - "serde_derive", - "target-lexicon", - "thiserror", - "wasmparser", - "wasmtime-types", -] - -[[package]] -name = "wasmtime-jit" -version = "16.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "anyhow", - "bincode", - "cfg-if 1.0.0", - "gimli", - "log", - "object", - "rustix 0.38.21", - "serde", - "serde_derive", - "target-lexicon", - "wasmtime-environ", - "wasmtime-jit-icache-coherence", - "wasmtime-runtime", - "windows-sys 0.48.0", -] - -[[package]] -name = "wasmtime-jit-icache-coherence" -version = "16.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "wasmtime-runtime" -version = "16.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "anyhow", - "cc", - "cfg-if 1.0.0", - "indexmap 2.0.0", - "libc", - "log", - "mach", - "memfd", - "memoffset 0.9.0", - "paste", - "psm", - "rustix 0.38.21", - "sptr", - "wasm-encoder", - "wasmtime-asm-macros", - "wasmtime-environ", - "wasmtime-versioned-export-macros", - "wasmtime-wmemcheck", - "windows-sys 0.48.0", -] - -[[package]] -name = "wasmtime-types" -version = "16.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "cranelift-entity", - "serde", - "serde_derive", - "thiserror", - "wasmparser", -] - -[[package]] -name = "wasmtime-versioned-export-macros" -version = "16.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.37", -] - -[[package]] -name = "wasmtime-wmemcheck" -version = "16.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime?rev=v16.0.0#6613acd1e4817957a4a7745125ef063b43c273a7" - [[package]] name = "web-sys" version = "0.3.64" @@ -9924,7 +9514,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.120.0" +version = "0.119.0" dependencies = [ "activity_indicator", "ai", diff --git a/Cargo.toml b/Cargo.toml index 5449f82b2fa3af1c08bcfc6571708fa492da03f1..9f2bc145901cdcfa4fc7c02649edcdbbbb73dc07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,12 +127,11 @@ thiserror = { version = "1.0.29" } time = { version = "0.3", features = ["serde", "serde-well-known"] } toml = { version = "0.5" } tiktoken-rs = "0.5.7" -tree-sitter = { version = "0.20", features = ["wasm"] } +tree-sitter = { version = "0.20" } unindent = { version = "0.1.7" } pretty_assertions = "1.3.0" git2 = { version = "0.15", default-features = false} uuid = { version = "1.1.2", features = ["v4"] } -wasmtime = "16" tree-sitter-bash = { git = "https://github.com/tree-sitter/tree-sitter-bash", rev = "7331995b19b8f8aba2d5e26deb51d2195c18bc94" } tree-sitter-c = "0.20.1" @@ -166,7 +165,7 @@ tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "31c40449749c4263a91a43593831b82229049a4c" } async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } -wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" } +# wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 cocoa = { git = "https://github.com/servo/core-foundation-rs", rev = "079665882507dd5e2ff77db3de5070c1f6c0fb85" } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 2a75050bb00113ffcf9e91d65b812319e83f3acd..5564481c6bdc07a41d0b3fdd91a75970de9b8e21 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -44,7 +44,7 @@ use std::{ }; use syntax_map::SyntaxSnapshot; use theme::{SyntaxTheme, Theme}; -use tree_sitter::{self, wasmtime, Query, WasmStore}; +use tree_sitter::{self, Query}; use unicase::UniCase; use util::{http::HttpClient, paths::PathExt}; use util::{post_inc, ResultExt, TryFutureExt as _, UnwrapFuture}; @@ -85,14 +85,11 @@ impl LspBinaryStatusSender { thread_local! { static PARSER: RefCell = { - let mut parser = Parser::new(); - parser.set_wasm_store(WasmStore::new(WASM_ENGINE.clone()).unwrap()).unwrap(); - RefCell::new(parser) + RefCell::new(Parser::new()) }; } lazy_static! { - pub static ref WASM_ENGINE: wasmtime::Engine = wasmtime::Engine::default(); pub static ref NEXT_GRAMMAR_ID: AtomicUsize = Default::default(); pub static ref PLAIN_TEXT: Arc = Arc::new(Language::new( LanguageConfig { @@ -641,8 +638,8 @@ enum AvailableGrammar { get_queries: fn(&str) -> LanguageQueries, }, Wasm { - grammar_name: Arc, - path: Arc, + _grammar_name: Arc, + _path: Arc, }, } @@ -742,7 +739,10 @@ impl LanguageRegistry { state.available_languages.push(AvailableLanguage { id: post_inc(&mut state.next_available_language_id), config, - grammar: AvailableGrammar::Wasm { grammar_name, path }, + grammar: AvailableGrammar::Wasm { + _grammar_name: grammar_name, + _path: path, + }, lsp_adapters: Vec::new(), loaded: false, }); @@ -876,25 +876,8 @@ impl LanguageRegistry { asset_dir, get_queries, } => (grammar, (get_queries)(asset_dir)), - AvailableGrammar::Wasm { grammar_name, path } => { - let mut wasm_path = path.join(grammar_name.as_ref()); - wasm_path.set_extension("wasm"); - let wasm_bytes = std::fs::read(&wasm_path)?; - let grammar = PARSER.with(|parser| { - let mut parser = parser.borrow_mut(); - let mut store = parser.take_wasm_store().unwrap(); - let grammar = - store.load_language(&grammar_name, &wasm_bytes); - parser.set_wasm_store(store).unwrap(); - grammar - })?; - let mut queries = LanguageQueries::default(); - if let Ok(contents) = std::fs::read_to_string( - &path.join("highlights.scm"), - ) { - queries.highlights = Some(contents.into()); - } - (grammar, queries) + AvailableGrammar::Wasm { .. } => { + Err(anyhow!("not supported"))? } }; Language::new(language.config, Some(grammar)) From f633043859be54dcb51e56efe4c399bc61caa06e Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 3 Jan 2024 22:44:31 -0500 Subject: [PATCH 04/67] Update ui docs --- crates/ui/docs/todo.md | 25 ------------------------- crates/ui/src/ui.rs | 7 ------- 2 files changed, 32 deletions(-) delete mode 100644 crates/ui/docs/todo.md diff --git a/crates/ui/docs/todo.md b/crates/ui/docs/todo.md deleted file mode 100644 index e7a053ecf47d212e9527733a629482c57a2359eb..0000000000000000000000000000000000000000 --- a/crates/ui/docs/todo.md +++ /dev/null @@ -1,25 +0,0 @@ -## Documentation priorities: - -These are the priorities to get documented, in a rough stack rank order: - -- [ ] label -- [ ] button -- [ ] icon_button -- [ ] icon -- [ ] list -- [ ] avatar -- [ ] panel -- [ ] modal -- [ ] palette -- [ ] input -- [ ] facepile -- [ ] player -- [ ] stacks -- [ ] context menu -- [ ] input -- [ ] textarea/multiline input (not built - not an editor) -- [ ] indicator -- [ ] public actor -- [ ] keybinding -- [ ] tab -- [ ] toast diff --git a/crates/ui/src/ui.rs b/crates/ui/src/ui.rs index b34e66dbdaaf990b6c87cfd13be0907d1b2e6cbb..b074f10dad50b9e61d0ed69ca29f39687e54973e 100644 --- a/crates/ui/src/ui.rs +++ b/crates/ui/src/ui.rs @@ -2,15 +2,8 @@ //! //! This crate provides a set of UI primitives and components that are used to build all of the elements in Zed's UI. //! -//! ## Work in Progress -//! -//! This crate is still a work in progress. The initial primitives and components are built for getting all the UI on the screen, -//! much of the state and functionality is mocked or hard codeded, and performance has not been a focus. -//! -#![doc = include_str!("../docs/hello-world.md")] #![doc = include_str!("../docs/building-ui.md")] -#![doc = include_str!("../docs/todo.md")] mod clickable; mod components; From bea527d647dd6ded8bac146f77d85f574b34d52c Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 3 Jan 2024 22:56:33 -0500 Subject: [PATCH 05/67] Update version in Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 1d0af3c00876104d2f8554ee43ed757689bce281..13d4be62337ae0557fbc59538db4a4bdceacd22e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9514,7 +9514,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.119.0" +version = "0.120.0" dependencies = [ "activity_indicator", "ai", From e903adf016cb41ba567a244c9ee4c2cbd52c4781 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 3 Jan 2024 23:06:16 -0500 Subject: [PATCH 06/67] Use dense spacing for completion menu items (#3874) This PR updates the completion menu to use dense spacing for its items. Release Notes: - Adjusted styling of completion menu entries. --- crates/editor/src/editor.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 85a156a8eb9014728a4a2ad1598082e990f45ef7..8ca8ba073166b39f03f181bbfe2c805857e0048a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -99,9 +99,9 @@ use sum_tree::TreeMap; use text::{OffsetUtf16, Rope}; use theme::{ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings}; use ui::{ - h_stack, ButtonSize, ButtonStyle, Icon, IconButton, ListItem, ListItemSpacing, Popover, Tooltip, + h_stack, prelude::*, ButtonSize, ButtonStyle, Icon, IconButton, IconSize, ListItem, Popover, + Tooltip, }; -use ui::{prelude::*, IconSize}; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{searchable::SearchEvent, ItemNavHistory, Pane, SplitDirection, ViewId, Workspace}; @@ -1259,7 +1259,6 @@ impl CompletionsMenu { div().min_w(px(220.)).max_w(px(540.)).child( ListItem::new(mat.candidate_id) .inset(true) - .spacing(ListItemSpacing::Sparse) .selected(item_ix == selected_item) .on_click(cx.listener(move |editor, _event, cx| { cx.stop_propagation(); From 710a26ce32244835891469c26484d4538844c126 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 3 Jan 2024 23:07:21 -0500 Subject: [PATCH 07/67] Add "Checking" icon for diagnostics --- Cargo.lock | 2 +- assets/icons/arrow_circle.svg | 1 + crates/diagnostics/src/items.rs | 21 ++++++++++++++++----- crates/ui/src/components/icon.rs | 2 ++ 4 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 assets/icons/arrow_circle.svg diff --git a/Cargo.lock b/Cargo.lock index 1d0af3c00876104d2f8554ee43ed757689bce281..13d4be62337ae0557fbc59538db4a4bdceacd22e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9514,7 +9514,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.119.0" +version = "0.120.0" dependencies = [ "activity_indicator", "ai", diff --git a/assets/icons/arrow_circle.svg b/assets/icons/arrow_circle.svg new file mode 100644 index 0000000000000000000000000000000000000000..750e349e2b8c73ef0c78b9974ea100f70ae37abe --- /dev/null +++ b/assets/icons/arrow_circle.svg @@ -0,0 +1 @@ + diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 9f7be0c04f2ca27413953cd5a133139cba4e10ea..da1f77b9afb0f1c4308362f42f479dc30890e77b 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -23,11 +23,21 @@ pub struct DiagnosticIndicator { impl Render for DiagnosticIndicator { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let diagnostic_indicator = match (self.summary.error_count, self.summary.warning_count) { - (0, 0) => h_stack().child( - IconElement::new(Icon::Check) - .size(IconSize::Small) - .color(Color::Success), - ), + (0, 0) => h_stack().map(|this| { + if !self.in_progress_checks.is_empty() { + this.child( + IconElement::new(Icon::ArrowCircle) + .size(IconSize::Small) + .color(Color::Muted), + ) + } else { + this.child( + IconElement::new(Icon::Check) + .size(IconSize::Small) + .color(Color::Default), + ) + } + }), (0, warning_count) => h_stack() .gap_1() .child( @@ -64,6 +74,7 @@ impl Render for DiagnosticIndicator { Some( Label::new("Checking…") .size(LabelSize::Small) + .color(Color::Muted) .into_any_element(), ) } else if let Some(diagnostic) = &self.current_diagnostic { diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index 9c0b1e58e301306bbcd7b5494bafc0ba5c1a582f..4c6e48c0fc034dbcfbd4454c920b2d4a994b92b0 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -29,6 +29,7 @@ pub enum Icon { ArrowRight, ArrowUp, ArrowUpRight, + ArrowCircle, AtSign, AudioOff, AudioOn, @@ -119,6 +120,7 @@ impl Icon { Icon::ArrowRight => "icons/arrow_right.svg", Icon::ArrowUp => "icons/arrow_up.svg", Icon::ArrowUpRight => "icons/arrow_up_right.svg", + Icon::ArrowCircle => "icons/arrow_circle.svg", Icon::AtSign => "icons/at_sign.svg", Icon::AudioOff => "icons/speaker_off.svg", Icon::AudioOn => "icons/speaker_loud.svg", From d643d99943dfb609ffb946b5806177e19e55e162 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 3 Jan 2024 23:09:24 -0500 Subject: [PATCH 08/67] Make scrollbar track transparent --- crates/theme/src/themes/andromeda.rs | 2 +- crates/theme/src/themes/atelier.rs | 40 ++++++++++----------- crates/theme/src/themes/ayu.rs | 6 ++-- crates/theme/src/themes/gruvbox.rs | 12 +++---- crates/theme/src/themes/one.rs | 4 +-- crates/theme/src/themes/rose_pine.rs | 6 ++-- crates/theme/src/themes/sandcastle.rs | 2 +- crates/theme/src/themes/solarized.rs | 4 +-- crates/theme/src/themes/summercamp.rs | 2 +- crates/theme_importer/src/zed1/converter.rs | 2 +- 10 files changed, 40 insertions(+), 40 deletions(-) diff --git a/crates/theme/src/themes/andromeda.rs b/crates/theme/src/themes/andromeda.rs index e46e9cf9d0977bb527eb34d3a3697916a81b96a1..e61ac8508fef0ad46b89981efe8ad06f47c27353 100644 --- a/crates/theme/src/themes/andromeda.rs +++ b/crates/theme/src/themes/andromeda.rs @@ -59,7 +59,7 @@ pub fn andromeda() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xf7f7f84c).into()), scrollbar_thumb_hover_background: Some(rgba(0x252931ff).into()), scrollbar_thumb_border: Some(rgba(0x252931ff).into()), - scrollbar_track_background: Some(rgba(0x1e2025ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x21232aff).into()), editor_foreground: Some(rgba(0xf7f7f8ff).into()), editor_background: Some(rgba(0x1e2025ff).into()), diff --git a/crates/theme/src/themes/atelier.rs b/crates/theme/src/themes/atelier.rs index 75226c669bbe7aa32119d70f85fb63ccbf2e6190..1bd0e2e455ddb72ecf635721daadbcf7ba445a1f 100644 --- a/crates/theme/src/themes/atelier.rs +++ b/crates/theme/src/themes/atelier.rs @@ -60,7 +60,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x22221b4c).into()), scrollbar_thumb_hover_background: Some(rgba(0xd1d0c6ff).into()), scrollbar_thumb_border: Some(rgba(0xd1d0c6ff).into()), - scrollbar_track_background: Some(rgba(0xf4f3ecff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xedece5ff).into()), editor_foreground: Some(rgba(0x302f27ff).into()), editor_background: Some(rgba(0xf4f3ecff).into()), @@ -525,7 +525,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xf1efee4c).into()), scrollbar_thumb_hover_background: Some(rgba(0x3b3431ff).into()), scrollbar_thumb_border: Some(rgba(0x3b3431ff).into()), - scrollbar_track_background: Some(rgba(0x1b1918ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x251f1dff).into()), editor_foreground: Some(rgba(0xe6e2e0ff).into()), editor_background: Some(rgba(0x1b1918ff).into()), @@ -990,7 +990,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x171c194c).into()), scrollbar_thumb_hover_background: Some(rgba(0xc8d1cbff).into()), scrollbar_thumb_border: Some(rgba(0xc8d1cbff).into()), - scrollbar_track_background: Some(rgba(0xecf4eeff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xe5ede7ff).into()), editor_foreground: Some(rgba(0x232a25ff).into()), editor_background: Some(rgba(0xecf4eeff).into()), @@ -1455,7 +1455,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xefecf44c).into()), scrollbar_thumb_hover_background: Some(rgba(0x332f38ff).into()), scrollbar_thumb_border: Some(rgba(0x332f38ff).into()), - scrollbar_track_background: Some(rgba(0x19171cff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x201e24ff).into()), editor_foreground: Some(rgba(0xe2dfe7ff).into()), editor_background: Some(rgba(0x19171cff).into()), @@ -1920,7 +1920,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xf4f3ec4c).into()), scrollbar_thumb_hover_background: Some(rgba(0x3c3b31ff).into()), scrollbar_thumb_border: Some(rgba(0x3c3b31ff).into()), - scrollbar_track_background: Some(rgba(0x22221bff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x2a2922ff).into()), editor_foreground: Some(rgba(0xe7e6dfff).into()), editor_background: Some(rgba(0x22221bff).into()), @@ -2385,7 +2385,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xf5f7ff4c).into()), scrollbar_thumb_hover_background: Some(rgba(0x363f62ff).into()), scrollbar_thumb_border: Some(rgba(0x363f62ff).into()), - scrollbar_track_background: Some(rgba(0x202746ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x252d4fff).into()), editor_foreground: Some(rgba(0xdfe2f1ff).into()), editor_background: Some(rgba(0x202746ff).into()), @@ -2850,7 +2850,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x2027464c).into()), scrollbar_thumb_hover_background: Some(rgba(0xccd0e1ff).into()), scrollbar_thumb_border: Some(rgba(0xccd0e1ff).into()), - scrollbar_track_background: Some(rgba(0xf5f7ffff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xe9ebf7ff).into()), editor_foreground: Some(rgba(0x293256ff).into()), editor_background: Some(rgba(0xf5f7ffff).into()), @@ -3315,7 +3315,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xfefbec4c).into()), scrollbar_thumb_hover_background: Some(rgba(0x3b3933ff).into()), scrollbar_thumb_border: Some(rgba(0x3b3933ff).into()), - scrollbar_track_background: Some(rgba(0x20201dff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x252521ff).into()), editor_foreground: Some(rgba(0xe8e4cfff).into()), editor_background: Some(rgba(0x20201dff).into()), @@ -3780,7 +3780,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xf4fbf44c).into()), scrollbar_thumb_hover_background: Some(rgba(0x333b33ff).into()), scrollbar_thumb_border: Some(rgba(0x333b33ff).into()), - scrollbar_track_background: Some(rgba(0x131513ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x1d201dff).into()), editor_foreground: Some(rgba(0xcfe8cfff).into()), editor_background: Some(rgba(0x131513ff).into()), @@ -4245,7 +4245,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x19171c4c).into()), scrollbar_thumb_hover_background: Some(rgba(0xcbc8d1ff).into()), scrollbar_thumb_border: Some(rgba(0xcbc8d1ff).into()), - scrollbar_track_background: Some(rgba(0xefecf4ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xe8e5edff).into()), editor_foreground: Some(rgba(0x26232aff).into()), editor_background: Some(rgba(0xefecf4ff).into()), @@ -4710,7 +4710,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xf4ecec4c).into()), scrollbar_thumb_hover_background: Some(rgba(0x352f2fff).into()), scrollbar_thumb_border: Some(rgba(0x352f2fff).into()), - scrollbar_track_background: Some(rgba(0x1b1818ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x231f1fff).into()), editor_foreground: Some(rgba(0xe7dfdfff).into()), editor_background: Some(rgba(0x1b1818ff).into()), @@ -5175,7 +5175,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xf7f3f74c).into()), scrollbar_thumb_hover_background: Some(rgba(0x393239ff).into()), scrollbar_thumb_border: Some(rgba(0x393239ff).into()), - scrollbar_track_background: Some(rgba(0x1b181bff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x231e23ff).into()), editor_foreground: Some(rgba(0xd8cad8ff).into()), editor_background: Some(rgba(0x1b181bff).into()), @@ -5640,7 +5640,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xebf8ff4c).into()), scrollbar_thumb_hover_background: Some(rgba(0x2c3b42ff).into()), scrollbar_thumb_border: Some(rgba(0x2c3b42ff).into()), - scrollbar_track_background: Some(rgba(0x161b1dff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x1b2327ff).into()), editor_foreground: Some(rgba(0xc1e4f6ff).into()), editor_background: Some(rgba(0x161b1dff).into()), @@ -6105,7 +6105,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x1b19184c).into()), scrollbar_thumb_hover_background: Some(rgba(0xd6d1cfff).into()), scrollbar_thumb_border: Some(rgba(0xd6d1cfff).into()), - scrollbar_track_background: Some(rgba(0xf1efeeff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xebe8e6ff).into()), editor_foreground: Some(rgba(0x2c2421ff).into()), editor_background: Some(rgba(0xf1efeeff).into()), @@ -6570,7 +6570,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x20201d4c).into()), scrollbar_thumb_hover_background: Some(rgba(0xd7d3beff).into()), scrollbar_thumb_border: Some(rgba(0xd7d3beff).into()), - scrollbar_track_background: Some(rgba(0xfefbecff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xf2eedcff).into()), editor_foreground: Some(rgba(0x292824ff).into()), editor_background: Some(rgba(0xfefbecff).into()), @@ -7035,7 +7035,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x1b18184c).into()), scrollbar_thumb_hover_background: Some(rgba(0xcfc7c7ff).into()), scrollbar_thumb_border: Some(rgba(0xcfc7c7ff).into()), - scrollbar_track_background: Some(rgba(0xf4ececff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xede5e5ff).into()), editor_foreground: Some(rgba(0x292424ff).into()), editor_background: Some(rgba(0xf4ececff).into()), @@ -7500,7 +7500,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x1315134c).into()), scrollbar_thumb_hover_background: Some(rgba(0xbed7beff).into()), scrollbar_thumb_border: Some(rgba(0xbed7beff).into()), - scrollbar_track_background: Some(rgba(0xf4fbf4ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xdff0dfff).into()), editor_foreground: Some(rgba(0x242924ff).into()), editor_background: Some(rgba(0xf4fbf4ff).into()), @@ -7965,7 +7965,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xecf4ee4c).into()), scrollbar_thumb_hover_background: Some(rgba(0x2f3832ff).into()), scrollbar_thumb_border: Some(rgba(0x2f3832ff).into()), - scrollbar_track_background: Some(rgba(0x171c19ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x1e2420ff).into()), editor_foreground: Some(rgba(0xdfe7e2ff).into()), editor_background: Some(rgba(0x171c19ff).into()), @@ -8430,7 +8430,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x1b181b4c).into()), scrollbar_thumb_hover_background: Some(rgba(0xcdbecdff).into()), scrollbar_thumb_border: Some(rgba(0xcdbecdff).into()), - scrollbar_track_background: Some(rgba(0xf7f3f7ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xe5dce5ff).into()), editor_foreground: Some(rgba(0x292329ff).into()), editor_background: Some(rgba(0xf7f3f7ff).into()), @@ -8895,7 +8895,7 @@ pub fn atelier() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x161b1d4c).into()), scrollbar_thumb_hover_background: Some(rgba(0xb0d3e5ff).into()), scrollbar_thumb_border: Some(rgba(0xb0d3e5ff).into()), - scrollbar_track_background: Some(rgba(0xebf8ffff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xd3edfaff).into()), editor_foreground: Some(rgba(0x1f292eff).into()), editor_background: Some(rgba(0xebf8ffff).into()), diff --git a/crates/theme/src/themes/ayu.rs b/crates/theme/src/themes/ayu.rs index 9029a01e25f52b5814d2dacd2061d5d15a219450..995990e5a3ed4d9899b14e1ccf8659eb4bd74ee3 100644 --- a/crates/theme/src/themes/ayu.rs +++ b/crates/theme/src/themes/ayu.rs @@ -60,7 +60,7 @@ pub fn ayu() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xbfbdb64c).into()), scrollbar_thumb_hover_background: Some(rgba(0x2d2f34ff).into()), scrollbar_thumb_border: Some(rgba(0x2d2f34ff).into()), - scrollbar_track_background: Some(rgba(0x0d1017ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x1b1e24ff).into()), editor_foreground: Some(rgba(0xbfbdb6ff).into()), editor_background: Some(rgba(0x0d1017ff).into()), @@ -504,7 +504,7 @@ pub fn ayu() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x5c61664c).into()), scrollbar_thumb_hover_background: Some(rgba(0xdfe0e1ff).into()), scrollbar_thumb_border: Some(rgba(0xdfe0e1ff).into()), - scrollbar_track_background: Some(rgba(0xfcfcfcff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xefeff0ff).into()), editor_foreground: Some(rgba(0x5c6166ff).into()), editor_background: Some(rgba(0xfcfcfcff).into()), @@ -948,7 +948,7 @@ pub fn ayu() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xcccac24c).into()), scrollbar_thumb_hover_background: Some(rgba(0x43464fff).into()), scrollbar_thumb_border: Some(rgba(0x43464fff).into()), - scrollbar_track_background: Some(rgba(0x242936ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x323641ff).into()), editor_foreground: Some(rgba(0xcccac2ff).into()), editor_background: Some(rgba(0x242936ff).into()), diff --git a/crates/theme/src/themes/gruvbox.rs b/crates/theme/src/themes/gruvbox.rs index 615c29dea805eb5336532dac4793a0ea2cba6cd6..44d3defbac0ca97c499c7edc6bac6b67d09a3e17 100644 --- a/crates/theme/src/themes/gruvbox.rs +++ b/crates/theme/src/themes/gruvbox.rs @@ -60,7 +60,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x2828284c).into()), scrollbar_thumb_hover_background: Some(rgba(0xddcca7ff).into()), scrollbar_thumb_border: Some(rgba(0xddcca7ff).into()), - scrollbar_track_background: Some(rgba(0xf9f5d7ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xefe2bcff).into()), editor_foreground: Some(rgba(0x282828ff).into()), editor_background: Some(rgba(0xf9f5d7ff).into()), @@ -511,7 +511,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xfbf1c74c).into()), scrollbar_thumb_hover_background: Some(rgba(0x494340ff).into()), scrollbar_thumb_border: Some(rgba(0x494340ff).into()), - scrollbar_track_background: Some(rgba(0x32302fff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x393634ff).into()), editor_foreground: Some(rgba(0xebdbb2ff).into()), editor_background: Some(rgba(0x32302fff).into()), @@ -962,7 +962,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x2828284c).into()), scrollbar_thumb_hover_background: Some(rgba(0xddcca7ff).into()), scrollbar_thumb_border: Some(rgba(0xddcca7ff).into()), - scrollbar_track_background: Some(rgba(0xfbf1c7ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xefe1b8ff).into()), editor_foreground: Some(rgba(0x282828ff).into()), editor_background: Some(rgba(0xfbf1c7ff).into()), @@ -1413,7 +1413,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xfbf1c74c).into()), scrollbar_thumb_hover_background: Some(rgba(0x494340ff).into()), scrollbar_thumb_border: Some(rgba(0x494340ff).into()), - scrollbar_track_background: Some(rgba(0x282828ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x373432ff).into()), editor_foreground: Some(rgba(0xebdbb2ff).into()), editor_background: Some(rgba(0x282828ff).into()), @@ -1864,7 +1864,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x2828284c).into()), scrollbar_thumb_hover_background: Some(rgba(0xddcca7ff).into()), scrollbar_thumb_border: Some(rgba(0xddcca7ff).into()), - scrollbar_track_background: Some(rgba(0xf2e5bcff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xeddeb5ff).into()), editor_foreground: Some(rgba(0x282828ff).into()), editor_background: Some(rgba(0xf2e5bcff).into()), @@ -2315,7 +2315,7 @@ pub fn gruvbox() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xfbf1c74c).into()), scrollbar_thumb_hover_background: Some(rgba(0x494340ff).into()), scrollbar_thumb_border: Some(rgba(0x494340ff).into()), - scrollbar_track_background: Some(rgba(0x1d2021ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x343130ff).into()), editor_foreground: Some(rgba(0xebdbb2ff).into()), editor_background: Some(rgba(0x1d2021ff).into()), diff --git a/crates/theme/src/themes/one.rs b/crates/theme/src/themes/one.rs index 12fd7e9b663dbc9879a6b3110cd9e55b2a975e68..247acd1ceb519e9329f92177d26bc518972fa8ed 100644 --- a/crates/theme/src/themes/one.rs +++ b/crates/theme/src/themes/one.rs @@ -60,7 +60,7 @@ pub fn one() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x383a414c).into()), scrollbar_thumb_hover_background: Some(rgba(0xdfdfe0ff).into()), scrollbar_thumb_border: Some(rgba(0xdfdfe0ff).into()), - scrollbar_track_background: Some(rgba(0xfafafaff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xeeeeeeff).into()), editor_foreground: Some(rgba(0x383a41ff).into()), editor_background: Some(rgba(0xfafafaff).into()), @@ -511,7 +511,7 @@ pub fn one() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xc8ccd44c).into()), scrollbar_thumb_hover_background: Some(rgba(0x363c46ff).into()), scrollbar_thumb_border: Some(rgba(0x363c46ff).into()), - scrollbar_track_background: Some(rgba(0x282c34ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x2e333cff).into()), editor_foreground: Some(rgba(0xacb2beff).into()), editor_background: Some(rgba(0x282c34ff).into()), diff --git a/crates/theme/src/themes/rose_pine.rs b/crates/theme/src/themes/rose_pine.rs index 966d6e5e625b5ffa5dbb9c4ae1ef5d0ff3e2aaec..5bdb89b8a60a758462d27341fa8b7f51bc0281cf 100644 --- a/crates/theme/src/themes/rose_pine.rs +++ b/crates/theme/src/themes/rose_pine.rs @@ -60,7 +60,7 @@ pub fn rose_pine() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x5752794c).into()), scrollbar_thumb_hover_background: Some(rgba(0xe5e0dfff).into()), scrollbar_thumb_border: Some(rgba(0xe5e0dfff).into()), - scrollbar_track_background: Some(rgba(0xfaf4edff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xfdf8f1ff).into()), editor_foreground: Some(rgba(0x575279ff).into()), editor_background: Some(rgba(0xfaf4edff).into()), @@ -518,7 +518,7 @@ pub fn rose_pine() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xe0def44c).into()), scrollbar_thumb_hover_background: Some(rgba(0x322f48ff).into()), scrollbar_thumb_border: Some(rgba(0x322f48ff).into()), - scrollbar_track_background: Some(rgba(0x232136ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x27243bff).into()), editor_foreground: Some(rgba(0xe0def4ff).into()), editor_background: Some(rgba(0x232136ff).into()), @@ -976,7 +976,7 @@ pub fn rose_pine() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xe0def44c).into()), scrollbar_thumb_hover_background: Some(rgba(0x232132ff).into()), scrollbar_thumb_border: Some(rgba(0x232132ff).into()), - scrollbar_track_background: Some(rgba(0x191724ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x1c1a29ff).into()), editor_foreground: Some(rgba(0xe0def4ff).into()), editor_background: Some(rgba(0x191724ff).into()), diff --git a/crates/theme/src/themes/sandcastle.rs b/crates/theme/src/themes/sandcastle.rs index 9ca7a9dff2bb3483ec737d9b146985ae4fdaa965..d9660ce11eb59fd1af7b4555cb091f1f4f292c12 100644 --- a/crates/theme/src/themes/sandcastle.rs +++ b/crates/theme/src/themes/sandcastle.rs @@ -59,7 +59,7 @@ pub fn sandcastle() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xfdf4c14c).into()), scrollbar_thumb_hover_background: Some(rgba(0x313741ff).into()), scrollbar_thumb_border: Some(rgba(0x313741ff).into()), - scrollbar_track_background: Some(rgba(0x282c34ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x2a2f38ff).into()), editor_foreground: Some(rgba(0xfdf4c1ff).into()), editor_background: Some(rgba(0x282c34ff).into()), diff --git a/crates/theme/src/themes/solarized.rs b/crates/theme/src/themes/solarized.rs index 945d99ed5bd5da5eaa012fabf5527a6e25b722fd..acdc2b50755254fd2d39e8a0b79277969762d7af 100644 --- a/crates/theme/src/themes/solarized.rs +++ b/crates/theme/src/themes/solarized.rs @@ -60,7 +60,7 @@ pub fn solarized() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0x002b364c).into()), scrollbar_thumb_hover_background: Some(rgba(0xdcdacbff).into()), scrollbar_thumb_border: Some(rgba(0xdcdacbff).into()), - scrollbar_track_background: Some(rgba(0xfdf6e3ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0xf5eedbff).into()), editor_foreground: Some(rgba(0x002b36ff).into()), editor_background: Some(rgba(0xfdf6e3ff).into()), @@ -504,7 +504,7 @@ pub fn solarized() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xfdf6e34c).into()), scrollbar_thumb_hover_background: Some(rgba(0x063541ff).into()), scrollbar_thumb_border: Some(rgba(0x063541ff).into()), - scrollbar_track_background: Some(rgba(0x002b36ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x032f3bff).into()), editor_foreground: Some(rgba(0xfdf6e3ff).into()), editor_background: Some(rgba(0x002b36ff).into()), diff --git a/crates/theme/src/themes/summercamp.rs b/crates/theme/src/themes/summercamp.rs index 963608d5db1a4a5d5fffcda73bbb1de267a27761..d4a8de6e12eabc3519d412cfe03bf123f28cd368 100644 --- a/crates/theme/src/themes/summercamp.rs +++ b/crates/theme/src/themes/summercamp.rs @@ -59,7 +59,7 @@ pub fn summercamp() -> UserThemeFamily { scrollbar_thumb_background: Some(rgba(0xf8f5de4c).into()), scrollbar_thumb_hover_background: Some(rgba(0x29251bff).into()), scrollbar_thumb_border: Some(rgba(0x29251bff).into()), - scrollbar_track_background: Some(rgba(0x1c1810ff).into()), + scrollbar_track_background: Some(rgba(0x00000000).into()), scrollbar_track_border: Some(rgba(0x221e15ff).into()), editor_foreground: Some(rgba(0xf8f5deff).into()), editor_background: Some(rgba(0x1c1810ff).into()), diff --git a/crates/theme_importer/src/zed1/converter.rs b/crates/theme_importer/src/zed1/converter.rs index 45563971d17ceabdeecdb5fbfe31d0cb46d5451a..84cb760eaf42207242ff715e1bbd179167984cf5 100644 --- a/crates/theme_importer/src/zed1/converter.rs +++ b/crates/theme_importer/src/zed1/converter.rs @@ -230,7 +230,7 @@ impl Zed1ThemeConverter { .map(|color| color_alpha(color, 0.3)), scrollbar_thumb_hover_background: convert(middle.base.hovered.background), scrollbar_thumb_border: convert(middle.base.default.border), - scrollbar_track_background: convert(highest.base.default.background), + scrollbar_track_background: Some(gpui::transparent_black()), scrollbar_track_border: convert(highest.variant.default.border), editor_foreground: convert(editor.text_color), editor_background: convert(editor.background), From 20a897d511f9644324865b3857f053c6606501d1 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 3 Jan 2024 23:13:26 -0500 Subject: [PATCH 09/67] Update border variant color --- crates/theme/src/themes/andromeda.rs | 2 +- crates/theme/src/themes/atelier.rs | 40 ++++++++++----------- crates/theme/src/themes/ayu.rs | 6 ++-- crates/theme/src/themes/gruvbox.rs | 12 +++---- crates/theme/src/themes/one.rs | 4 +-- crates/theme/src/themes/rose_pine.rs | 6 ++-- crates/theme/src/themes/sandcastle.rs | 2 +- crates/theme/src/themes/solarized.rs | 4 +-- crates/theme/src/themes/summercamp.rs | 2 +- crates/theme_importer/src/zed1/converter.rs | 2 +- 10 files changed, 40 insertions(+), 40 deletions(-) diff --git a/crates/theme/src/themes/andromeda.rs b/crates/theme/src/themes/andromeda.rs index e61ac8508fef0ad46b89981efe8ad06f47c27353..effdfb85f97a0c9baa1d4b9881b6c89de9ec1245 100644 --- a/crates/theme/src/themes/andromeda.rs +++ b/crates/theme/src/themes/andromeda.rs @@ -20,7 +20,7 @@ pub fn andromeda() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x2b2f39ff).into()), - border_variant: Some(rgba(0x2b2f39ff).into()), + border_variant: Some(rgba(0x252931ff).into()), border_focused: Some(rgba(0x183a34ff).into()), border_selected: Some(rgba(0x183a34ff).into()), border_transparent: Some(rgba(0x00000000).into()), diff --git a/crates/theme/src/themes/atelier.rs b/crates/theme/src/themes/atelier.rs index 1bd0e2e455ddb72ecf635721daadbcf7ba445a1f..2d05a6b65be1daa7353ef6bb4775725d864b5dca 100644 --- a/crates/theme/src/themes/atelier.rs +++ b/crates/theme/src/themes/atelier.rs @@ -21,7 +21,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x969585ff).into()), - border_variant: Some(rgba(0x969585ff).into()), + border_variant: Some(rgba(0xd1d0c6ff).into()), border_focused: Some(rgba(0xbbddc6ff).into()), border_selected: Some(rgba(0xbbddc6ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -486,7 +486,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x665f5cff).into()), - border_variant: Some(rgba(0x665f5cff).into()), + border_variant: Some(rgba(0x3b3431ff).into()), border_focused: Some(rgba(0x192e5bff).into()), border_selected: Some(rgba(0x192e5bff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -951,7 +951,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x8b968eff).into()), - border_variant: Some(rgba(0x8b968eff).into()), + border_variant: Some(rgba(0xc8d1cbff).into()), border_focused: Some(rgba(0xbed4d6ff).into()), border_selected: Some(rgba(0xbed4d6ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -1416,7 +1416,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x56505eff).into()), - border_variant: Some(rgba(0x56505eff).into()), + border_variant: Some(rgba(0x332f38ff).into()), border_focused: Some(rgba(0x222953ff).into()), border_selected: Some(rgba(0x222953ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -1881,7 +1881,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x5d5c4cff).into()), - border_variant: Some(rgba(0x5d5c4cff).into()), + border_variant: Some(rgba(0x3c3b31ff).into()), border_focused: Some(rgba(0x1c3927ff).into()), border_selected: Some(rgba(0x1c3927ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -2346,7 +2346,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x5c6485ff).into()), - border_variant: Some(rgba(0x5c6485ff).into()), + border_variant: Some(rgba(0x363f62ff).into()), border_focused: Some(rgba(0x203348ff).into()), border_selected: Some(rgba(0x203348ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -2811,7 +2811,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x9a9fb6ff).into()), - border_variant: Some(rgba(0x9a9fb6ff).into()), + border_variant: Some(rgba(0xccd0e1ff).into()), border_focused: Some(rgba(0xc2d5efff).into()), border_selected: Some(rgba(0xc2d5efff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -3276,7 +3276,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x6c695cff).into()), - border_variant: Some(rgba(0x6c695cff).into()), + border_variant: Some(rgba(0x3b3933ff).into()), border_focused: Some(rgba(0x263056ff).into()), border_selected: Some(rgba(0x263056ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -3741,7 +3741,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x5c6c5cff).into()), - border_variant: Some(rgba(0x5c6c5cff).into()), + border_variant: Some(rgba(0x333b33ff).into()), border_focused: Some(rgba(0x102668ff).into()), border_selected: Some(rgba(0x102668ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -4206,7 +4206,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x8f8b96ff).into()), - border_variant: Some(rgba(0x8f8b96ff).into()), + border_variant: Some(rgba(0xcbc8d1ff).into()), border_focused: Some(rgba(0xc9c8f3ff).into()), border_selected: Some(rgba(0xc9c8f3ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -4671,7 +4671,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x564e4eff).into()), - border_variant: Some(rgba(0x564e4eff).into()), + border_variant: Some(rgba(0x352f2fff).into()), border_focused: Some(rgba(0x2c2b45ff).into()), border_selected: Some(rgba(0x2c2b45ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -5136,7 +5136,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x675b67ff).into()), - border_variant: Some(rgba(0x675b67ff).into()), + border_variant: Some(rgba(0x393239ff).into()), border_focused: Some(rgba(0x1a2961ff).into()), border_selected: Some(rgba(0x1a2961ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -5601,7 +5601,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x4f6b78ff).into()), - border_variant: Some(rgba(0x4f6b78ff).into()), + border_variant: Some(rgba(0x2c3b42ff).into()), border_focused: Some(rgba(0x1a2f3cff).into()), border_selected: Some(rgba(0x1a2f3cff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -6066,7 +6066,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0xaaa3a1ff).into()), - border_variant: Some(rgba(0xaaa3a1ff).into()), + border_variant: Some(rgba(0xd6d1cfff).into()), border_focused: Some(rgba(0xc6cef7ff).into()), border_selected: Some(rgba(0xc6cef7ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -6531,7 +6531,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0xa8a48eff).into()), - border_variant: Some(rgba(0xa8a48eff).into()), + border_variant: Some(rgba(0xd7d3beff).into()), border_focused: Some(rgba(0xcdd1f5ff).into()), border_selected: Some(rgba(0xcdd1f5ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -6996,7 +6996,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x8e8989ff).into()), - border_variant: Some(rgba(0x8e8989ff).into()), + border_variant: Some(rgba(0xcfc7c7ff).into()), border_focused: Some(rgba(0xcecaecff).into()), border_selected: Some(rgba(0xcecaecff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -7461,7 +7461,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x8ea88eff).into()), - border_variant: Some(rgba(0x8ea88eff).into()), + border_variant: Some(rgba(0xbed7beff).into()), border_focused: Some(rgba(0xc9c4fdff).into()), border_selected: Some(rgba(0xc9c4fdff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -7926,7 +7926,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x505e55ff).into()), - border_variant: Some(rgba(0x505e55ff).into()), + border_variant: Some(rgba(0x2f3832ff).into()), border_focused: Some(rgba(0x1f3233ff).into()), border_selected: Some(rgba(0x1f3233ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -8391,7 +8391,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0xad9dadff).into()), - border_variant: Some(rgba(0xad9dadff).into()), + border_variant: Some(rgba(0xcdbecdff).into()), border_focused: Some(rgba(0xcac7faff).into()), border_selected: Some(rgba(0xcac7faff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -8856,7 +8856,7 @@ pub fn atelier() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x80a4b6ff).into()), - border_variant: Some(rgba(0x80a4b6ff).into()), + border_variant: Some(rgba(0xb0d3e5ff).into()), border_focused: Some(rgba(0xbacfe1ff).into()), border_selected: Some(rgba(0xbacfe1ff).into()), border_transparent: Some(rgba(0x00000000).into()), diff --git a/crates/theme/src/themes/ayu.rs b/crates/theme/src/themes/ayu.rs index 995990e5a3ed4d9899b14e1ccf8659eb4bd74ee3..a0402825c1a1eaf5e6f91986c47505c68772c743 100644 --- a/crates/theme/src/themes/ayu.rs +++ b/crates/theme/src/themes/ayu.rs @@ -21,7 +21,7 @@ pub fn ayu() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x3f4043ff).into()), - border_variant: Some(rgba(0x3f4043ff).into()), + border_variant: Some(rgba(0x2d2f34ff).into()), border_focused: Some(rgba(0x1b4a6eff).into()), border_selected: Some(rgba(0x1b4a6eff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -465,7 +465,7 @@ pub fn ayu() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0xcfd1d2ff).into()), - border_variant: Some(rgba(0xcfd1d2ff).into()), + border_variant: Some(rgba(0xdfe0e1ff).into()), border_focused: Some(rgba(0xc4daf6ff).into()), border_selected: Some(rgba(0xc4daf6ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -909,7 +909,7 @@ pub fn ayu() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x53565dff).into()), - border_variant: Some(rgba(0x53565dff).into()), + border_variant: Some(rgba(0x43464fff).into()), border_focused: Some(rgba(0x24556fff).into()), border_selected: Some(rgba(0x24556fff).into()), border_transparent: Some(rgba(0x00000000).into()), diff --git a/crates/theme/src/themes/gruvbox.rs b/crates/theme/src/themes/gruvbox.rs index 44d3defbac0ca97c499c7edc6bac6b67d09a3e17..b6b76bf47207918e289470471f715d2cfbfc1725 100644 --- a/crates/theme/src/themes/gruvbox.rs +++ b/crates/theme/src/themes/gruvbox.rs @@ -21,7 +21,7 @@ pub fn gruvbox() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0xc9b99aff).into()), - border_variant: Some(rgba(0xc9b99aff).into()), + border_variant: Some(rgba(0xddcca7ff).into()), border_focused: Some(rgba(0xaec6cdff).into()), border_selected: Some(rgba(0xaec6cdff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -472,7 +472,7 @@ pub fn gruvbox() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x5b534dff).into()), - border_variant: Some(rgba(0x5b534dff).into()), + border_variant: Some(rgba(0x494340ff).into()), border_focused: Some(rgba(0x303a36ff).into()), border_selected: Some(rgba(0x303a36ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -923,7 +923,7 @@ pub fn gruvbox() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0xc9b99aff).into()), - border_variant: Some(rgba(0xc9b99aff).into()), + border_variant: Some(rgba(0xddcca7ff).into()), border_focused: Some(rgba(0xaec6cdff).into()), border_selected: Some(rgba(0xaec6cdff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -1374,7 +1374,7 @@ pub fn gruvbox() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x5b534dff).into()), - border_variant: Some(rgba(0x5b534dff).into()), + border_variant: Some(rgba(0x494340ff).into()), border_focused: Some(rgba(0x303a36ff).into()), border_selected: Some(rgba(0x303a36ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -1825,7 +1825,7 @@ pub fn gruvbox() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0xc9b99aff).into()), - border_variant: Some(rgba(0xc9b99aff).into()), + border_variant: Some(rgba(0xddcca7ff).into()), border_focused: Some(rgba(0xaec6cdff).into()), border_selected: Some(rgba(0xaec6cdff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -2276,7 +2276,7 @@ pub fn gruvbox() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x5b534dff).into()), - border_variant: Some(rgba(0x5b534dff).into()), + border_variant: Some(rgba(0x494340ff).into()), border_focused: Some(rgba(0x303a36ff).into()), border_selected: Some(rgba(0x303a36ff).into()), border_transparent: Some(rgba(0x00000000).into()), diff --git a/crates/theme/src/themes/one.rs b/crates/theme/src/themes/one.rs index 247acd1ceb519e9329f92177d26bc518972fa8ed..c941fb294ddb32fd5638ee1ff92fe7a080121d4b 100644 --- a/crates/theme/src/themes/one.rs +++ b/crates/theme/src/themes/one.rs @@ -21,7 +21,7 @@ pub fn one() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0xc9c9caff).into()), - border_variant: Some(rgba(0xc9c9caff).into()), + border_variant: Some(rgba(0xdfdfe0ff).into()), border_focused: Some(rgba(0xcbcdf6ff).into()), border_selected: Some(rgba(0xcbcdf6ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -472,7 +472,7 @@ pub fn one() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x464b57ff).into()), - border_variant: Some(rgba(0x464b57ff).into()), + border_variant: Some(rgba(0x363c46ff).into()), border_focused: Some(rgba(0x293c5bff).into()), border_selected: Some(rgba(0x293c5bff).into()), border_transparent: Some(rgba(0x00000000).into()), diff --git a/crates/theme/src/themes/rose_pine.rs b/crates/theme/src/themes/rose_pine.rs index 5bdb89b8a60a758462d27341fa8b7f51bc0281cf..59b8a924377b6f105e8610717935391f1f2c0682 100644 --- a/crates/theme/src/themes/rose_pine.rs +++ b/crates/theme/src/themes/rose_pine.rs @@ -21,7 +21,7 @@ pub fn rose_pine() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0xdcd6d5ff).into()), - border_variant: Some(rgba(0xdcd6d5ff).into()), + border_variant: Some(rgba(0xe5e0dfff).into()), border_focused: Some(rgba(0xc3d7dbff).into()), border_selected: Some(rgba(0xc3d7dbff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -479,7 +479,7 @@ pub fn rose_pine() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x504c68ff).into()), - border_variant: Some(rgba(0x504c68ff).into()), + border_variant: Some(rgba(0x322f48ff).into()), border_focused: Some(rgba(0x435255ff).into()), border_selected: Some(rgba(0x435255ff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -937,7 +937,7 @@ pub fn rose_pine() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x423f55ff).into()), - border_variant: Some(rgba(0x423f55ff).into()), + border_variant: Some(rgba(0x232132ff).into()), border_focused: Some(rgba(0x435255ff).into()), border_selected: Some(rgba(0x435255ff).into()), border_transparent: Some(rgba(0x00000000).into()), diff --git a/crates/theme/src/themes/sandcastle.rs b/crates/theme/src/themes/sandcastle.rs index d9660ce11eb59fd1af7b4555cb091f1f4f292c12..bace9e936f8c26364cc57151b8bbc81c5cd21cc4 100644 --- a/crates/theme/src/themes/sandcastle.rs +++ b/crates/theme/src/themes/sandcastle.rs @@ -20,7 +20,7 @@ pub fn sandcastle() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x3d4350ff).into()), - border_variant: Some(rgba(0x3d4350ff).into()), + border_variant: Some(rgba(0x313741ff).into()), border_focused: Some(rgba(0x223232ff).into()), border_selected: Some(rgba(0x223232ff).into()), border_transparent: Some(rgba(0x00000000).into()), diff --git a/crates/theme/src/themes/solarized.rs b/crates/theme/src/themes/solarized.rs index acdc2b50755254fd2d39e8a0b79277969762d7af..c925785b176018f2813e396a22a35569504aca85 100644 --- a/crates/theme/src/themes/solarized.rs +++ b/crates/theme/src/themes/solarized.rs @@ -21,7 +21,7 @@ pub fn solarized() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x9faaa8ff).into()), - border_variant: Some(rgba(0x9faaa8ff).into()), + border_variant: Some(rgba(0xdcdacbff).into()), border_focused: Some(rgba(0xbfd3efff).into()), border_selected: Some(rgba(0xbfd3efff).into()), border_transparent: Some(rgba(0x00000000).into()), @@ -465,7 +465,7 @@ pub fn solarized() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x2b4f58ff).into()), - border_variant: Some(rgba(0x2b4f58ff).into()), + border_variant: Some(rgba(0x063541ff).into()), border_focused: Some(rgba(0x1c3249ff).into()), border_selected: Some(rgba(0x1c3249ff).into()), border_transparent: Some(rgba(0x00000000).into()), diff --git a/crates/theme/src/themes/summercamp.rs b/crates/theme/src/themes/summercamp.rs index d4a8de6e12eabc3519d412cfe03bf123f28cd368..2ee5f12394dd00328a2d2c698d44687aeeab94b6 100644 --- a/crates/theme/src/themes/summercamp.rs +++ b/crates/theme/src/themes/summercamp.rs @@ -20,7 +20,7 @@ pub fn summercamp() -> UserThemeFamily { styles: UserThemeStylesRefinement { colors: ThemeColorsRefinement { border: Some(rgba(0x312d21ff).into()), - border_variant: Some(rgba(0x312d21ff).into()), + border_variant: Some(rgba(0x29251bff).into()), border_focused: Some(rgba(0x193761ff).into()), border_selected: Some(rgba(0x193761ff).into()), border_transparent: Some(rgba(0x00000000).into()), diff --git a/crates/theme_importer/src/zed1/converter.rs b/crates/theme_importer/src/zed1/converter.rs index 84cb760eaf42207242ff715e1bbd179167984cf5..86c40dfde55a29cb7bb0c6745ae433ecba6553d3 100644 --- a/crates/theme_importer/src/zed1/converter.rs +++ b/crates/theme_importer/src/zed1/converter.rs @@ -187,7 +187,7 @@ impl Zed1ThemeConverter { Ok(ThemeColorsRefinement { border: convert(lowest.base.default.border), - border_variant: convert(lowest.variant.default.border), + border_variant: convert(middle.variant.default.border), border_focused: convert(lowest.accent.hovered.border), border_selected: convert(lowest.accent.default.border), border_transparent: Some(gpui::transparent_black()), From 77647fa088b65b01863fb9522ec56ca95f7d36df Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Wed, 3 Jan 2024 23:19:47 -0500 Subject: [PATCH 10/67] Use theme colors for muted/speaking indicators --- crates/assistant/src/assistant_panel.rs | 2 -- crates/collab_ui/src/collab_titlebar_item.rs | 7 +++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 7b19ad130c4c42316f6ca65c8063be9c3842b42b..4c7b183fc02a3bb390d99da2b5af06892887b05a 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -1125,8 +1125,6 @@ impl Render for AssistantPanel { .child(Label::new( "Click on the Z button in the status bar to close this panel." )) - .border() - .border_color(gpui::red()) } else { let header = TabBar::new("assistant_header") .start_child( diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 16d8be89af78db45bdcc27b317bffcbfdf66e98e..8150fe1e4ddf19bb422697d17cf0edf4ade0aed5 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -110,6 +110,7 @@ impl Render for CollabTitlebarItem { &room, project_id, ¤t_user, + cx, )) .children( remote_participants.iter().filter_map(|collaborator| { @@ -127,6 +128,7 @@ impl Render for CollabTitlebarItem { &room, project_id, ¤t_user, + cx, )?; Some( @@ -405,6 +407,7 @@ impl CollabTitlebarItem { room: &Room, project_id: Option, current_user: &Arc, + cx: &ViewContext, ) -> Option { let followers = project_id.map_or(&[] as &[_], |id| room.followers_for(peer_id, id)); @@ -413,9 +416,9 @@ impl CollabTitlebarItem { Avatar::new(user.avatar_uri.clone()) .grayscale(!is_present) .border_color(if is_speaking { - gpui::blue() + cx.theme().status().info_border } else if is_muted { - gpui::red() + cx.theme().status().error_border } else { Hsla::default() }), From 2972ee8cedfcac5666ec648065639fc8d519992c Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 4 Jan 2024 01:13:21 -0500 Subject: [PATCH 11/67] Move telemetry settings check into telemetry module --- crates/assistant/src/assistant_panel.rs | 11 +--- crates/call/src/call.rs | 9 +-- crates/client/src/telemetry.rs | 62 ++++++++------------- crates/editor/src/editor.rs | 13 +---- crates/theme_selector/src/theme_selector.rs | 7 +-- crates/workspace/src/workspace.rs | 5 +- crates/zed/src/main.rs | 11 ++-- 7 files changed, 41 insertions(+), 77 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 4c7b183fc02a3bb390d99da2b5af06892887b05a..3625b64e32ee52d1efad6d4c043c5147b2777ffc 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -16,7 +16,7 @@ use ai::{ use ai::prompts::repository_context::PromptCodeSnippet; use anyhow::{anyhow, Result}; use chrono::{DateTime, Local}; -use client::{telemetry::AssistantKind, TelemetrySettings}; +use client::telemetry::AssistantKind; use collections::{hash_map, HashMap, HashSet, VecDeque}; use editor::{ display_map::{ @@ -3527,12 +3527,5 @@ fn report_assistant_event( .default_open_ai_model .clone(); - let telemetry_settings = TelemetrySettings::get_global(cx).clone(); - - telemetry.report_assistant_event( - telemetry_settings, - conversation_id, - assistant_kind, - model.full_name(), - ) + telemetry.report_assistant_event(conversation_id, assistant_kind, model.full_name(), cx) } diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index bc8a0809cae179cd1faac96d90664c9ea41a30e6..c419043a722b35fb34f33a224502057e53f3a16b 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -5,7 +5,7 @@ pub mod room; use anyhow::{anyhow, Result}; use audio::Audio; use call_settings::CallSettings; -use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE}; +use client::{proto, Client, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE}; use collections::HashSet; use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use gpui::{ @@ -480,9 +480,8 @@ pub fn report_call_event_for_room( cx: &mut AppContext, ) { let telemetry = client.telemetry(); - let telemetry_settings = *TelemetrySettings::get_global(cx); - telemetry.report_call_event(telemetry_settings, operation, Some(room_id), channel_id) + telemetry.report_call_event(operation, Some(room_id), channel_id, cx) } pub fn report_call_event_for_channel( @@ -495,13 +494,11 @@ pub fn report_call_event_for_channel( let telemetry = client.telemetry(); - let telemetry_settings = *TelemetrySettings::get_global(cx); - telemetry.report_call_event( - telemetry_settings, operation, room.map(|r| r.read(cx).id()), Some(channel_id), + cx, ) } diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 789b627fb0641c4bfb3c99e70db8d96738b8c099..2391c5f3b55a0c96133ed82ae964ecd969cc68e2 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -177,8 +177,7 @@ impl Telemetry { // TestAppContext ends up calling this function on shutdown and it panics when trying to find the TelemetrySettings #[cfg(not(any(test, feature = "test-support")))] fn shutdown_telemetry(self: &Arc, cx: &mut AppContext) -> impl Future { - let telemetry_settings = TelemetrySettings::get_global(cx).clone(); - self.report_app_event(telemetry_settings, "close", true); + self.report_app_event("close", true, cx); Task::ready(()) } @@ -227,24 +226,11 @@ impl Telemetry { return; }; - let telemetry_settings = if let Ok(telemetry_settings) = - cx.update(|cx| *TelemetrySettings::get_global(cx)) - { - telemetry_settings - } else { - break; - }; - - this.report_memory_event( - telemetry_settings, - process.memory(), - process.virtual_memory(), - ); - this.report_cpu_event( - telemetry_settings, - process.cpu_usage(), - system.cpus().len() as u32, - ); + cx.update(|cx| { + this.report_memory_event(process.memory(), process.virtual_memory(), cx); + this.report_cpu_event(process.cpu_usage(), system.cpus().len() as u32, cx); + }) + .ok(); } }) .detach(); @@ -269,12 +255,12 @@ impl Telemetry { pub fn report_editor_event( self: &Arc, - telemetry_settings: TelemetrySettings, file_extension: Option, vim_mode: bool, operation: &'static str, copilot_enabled: bool, copilot_enabled_for_language: bool, + cx: &AppContext, ) { let event = ClickhouseEvent::Editor { file_extension, @@ -285,15 +271,15 @@ impl Telemetry { milliseconds_since_first_event: self.milliseconds_since_first_event(), }; - self.report_clickhouse_event(event, telemetry_settings, false) + self.report_clickhouse_event(event, false, cx) } pub fn report_copilot_event( self: &Arc, - telemetry_settings: TelemetrySettings, suggestion_id: Option, suggestion_accepted: bool, file_extension: Option, + cx: &AppContext, ) { let event = ClickhouseEvent::Copilot { suggestion_id, @@ -302,15 +288,15 @@ impl Telemetry { milliseconds_since_first_event: self.milliseconds_since_first_event(), }; - self.report_clickhouse_event(event, telemetry_settings, false) + self.report_clickhouse_event(event, false, cx) } pub fn report_assistant_event( self: &Arc, - telemetry_settings: TelemetrySettings, conversation_id: Option, kind: AssistantKind, model: &'static str, + cx: &AppContext, ) { let event = ClickhouseEvent::Assistant { conversation_id, @@ -319,15 +305,15 @@ impl Telemetry { milliseconds_since_first_event: self.milliseconds_since_first_event(), }; - self.report_clickhouse_event(event, telemetry_settings, false) + self.report_clickhouse_event(event, false, cx) } pub fn report_call_event( self: &Arc, - telemetry_settings: TelemetrySettings, operation: &'static str, room_id: Option, channel_id: Option, + cx: &AppContext, ) { let event = ClickhouseEvent::Call { operation, @@ -336,14 +322,14 @@ impl Telemetry { milliseconds_since_first_event: self.milliseconds_since_first_event(), }; - self.report_clickhouse_event(event, telemetry_settings, false) + self.report_clickhouse_event(event, false, cx) } pub fn report_cpu_event( self: &Arc, - telemetry_settings: TelemetrySettings, usage_as_percentage: f32, core_count: u32, + cx: &AppContext, ) { let event = ClickhouseEvent::Cpu { usage_as_percentage, @@ -351,14 +337,14 @@ impl Telemetry { milliseconds_since_first_event: self.milliseconds_since_first_event(), }; - self.report_clickhouse_event(event, telemetry_settings, false) + self.report_clickhouse_event(event, false, cx) } pub fn report_memory_event( self: &Arc, - telemetry_settings: TelemetrySettings, memory_in_bytes: u64, virtual_memory_in_bytes: u64, + cx: &AppContext, ) { let event = ClickhouseEvent::Memory { memory_in_bytes, @@ -366,28 +352,28 @@ impl Telemetry { milliseconds_since_first_event: self.milliseconds_since_first_event(), }; - self.report_clickhouse_event(event, telemetry_settings, false) + self.report_clickhouse_event(event, false, cx) } pub fn report_app_event( self: &Arc, - telemetry_settings: TelemetrySettings, operation: &'static str, immediate_flush: bool, + cx: &AppContext, ) { let event = ClickhouseEvent::App { operation, milliseconds_since_first_event: self.milliseconds_since_first_event(), }; - self.report_clickhouse_event(event, telemetry_settings, immediate_flush) + self.report_clickhouse_event(event, immediate_flush, cx) } pub fn report_setting_event( self: &Arc, - telemetry_settings: TelemetrySettings, setting: &'static str, value: String, + cx: &AppContext, ) { let event = ClickhouseEvent::Setting { setting, @@ -395,7 +381,7 @@ impl Telemetry { milliseconds_since_first_event: self.milliseconds_since_first_event(), }; - self.report_clickhouse_event(event, telemetry_settings, false) + self.report_clickhouse_event(event, false, cx) } fn milliseconds_since_first_event(&self) -> i64 { @@ -415,10 +401,10 @@ impl Telemetry { fn report_clickhouse_event( self: &Arc, event: ClickhouseEvent, - telemetry_settings: TelemetrySettings, immediate_flush: bool, + cx: &AppContext, ) { - if !telemetry_settings.metrics { + if !TelemetrySettings::get_global(cx).metrics { return; } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8ca8ba073166b39f03f181bbfe2c805857e0048a..1e537c7554dbd222d91d061717e455842d4eb666 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -24,7 +24,7 @@ use ::git::diff::DiffHunk; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Context as _, Result}; use blink_manager::BlinkManager; -use client::{Client, Collaborator, ParticipantIndex, TelemetrySettings}; +use client::{Client, Collaborator, ParticipantIndex}; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use convert_case::{Case, Casing}; @@ -8864,14 +8864,8 @@ impl Editor { .map(|a| a.to_string()); let telemetry = project.read(cx).client().telemetry().clone(); - let telemetry_settings = *TelemetrySettings::get_global(cx); - telemetry.report_copilot_event( - telemetry_settings, - suggestion_id, - suggestion_accepted, - file_extension, - ) + telemetry.report_copilot_event(suggestion_id, suggestion_accepted, file_extension, cx) } #[cfg(any(test, feature = "test-support"))] @@ -8909,7 +8903,6 @@ impl Editor { .raw_user_settings() .get("vim_mode") == Some(&serde_json::Value::Bool(true)); - let telemetry_settings = *TelemetrySettings::get_global(cx); let copilot_enabled = all_language_settings(file, cx).copilot_enabled(None, None); let copilot_enabled_for_language = self .buffer @@ -8919,12 +8912,12 @@ impl Editor { let telemetry = project.read(cx).client().telemetry().clone(); telemetry.report_editor_event( - telemetry_settings, file_extension, vim_mode, operation, copilot_enabled, copilot_enabled_for_language, + cx, ) } diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 44f2069694c421636e0714cd2f02093f577b02f7..cfb98ccd7455fd17c81a3de65a82b55f5d4bd877 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -1,4 +1,4 @@ -use client::{telemetry::Telemetry, TelemetrySettings}; +use client::telemetry::Telemetry; use feature_flags::FeatureFlagAppExt; use fs::Fs; use fuzzy::{match_strings, StringMatch, StringMatchCandidate}; @@ -7,7 +7,7 @@ use gpui::{ VisualContext, WeakView, }; use picker::{Picker, PickerDelegate}; -use settings::{update_settings_file, Settings, SettingsStore}; +use settings::{update_settings_file, SettingsStore}; use std::sync::Arc; use theme::{Theme, ThemeMeta, ThemeRegistry, ThemeSettings}; use ui::{prelude::*, v_stack, ListItem, ListItemSpacing}; @@ -181,9 +181,8 @@ impl PickerDelegate for ThemeSelectorDelegate { let theme_name = cx.theme().name.clone(); - let telemetry_settings = TelemetrySettings::get_global(cx).clone(); self.telemetry - .report_setting_event(telemetry_settings, "theme", theme_name.to_string()); + .report_setting_event("theme", theme_name.to_string(), cx); update_settings_file::(self.fs.clone(), cx, move |settings| { settings.theme = Some(theme_name.to_string()); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 76715f69bef9ffb5e7f4ced25d00372df897b7e5..9e3e59b309cf9a59662586ce14445e5fe70d1d96 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -15,7 +15,7 @@ use anyhow::{anyhow, Context as _, Result}; use call::ActiveCall; use client::{ proto::{self, PeerId}, - Client, Status, TelemetrySettings, TypedEnvelope, UserStore, + Client, Status, TypedEnvelope, UserStore, }; use collections::{hash_map, HashMap, HashSet}; use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle}; @@ -1250,10 +1250,9 @@ impl Workspace { } pub fn open(&mut self, _: &Open, cx: &mut ViewContext) { - let telemetry_settings = TelemetrySettings::get_global(cx).clone(); self.client() .telemetry() - .report_app_event(telemetry_settings, "open project", false); + .report_app_event("open project", false, cx); let paths = cx.prompt_for_paths(PathPromptOptions { files: true, directories: true, diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 5ac850b1c6d4b1702c8b697d618bedda809f0523..21e6c8fa216f5faaac67a1eb646389e20f6ca3eb 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -172,19 +172,16 @@ fn main() { .detach(); client.telemetry().start(installation_id, session_id, cx); - let telemetry_settings = *client::TelemetrySettings::get_global(cx); - client.telemetry().report_setting_event( - telemetry_settings, - "theme", - cx.theme().name.to_string(), - ); + client + .telemetry() + .report_setting_event("theme", cx.theme().name.to_string(), cx); let event_operation = match existing_installation_id_found { Some(false) => "first open", _ => "open", }; client .telemetry() - .report_app_event(telemetry_settings, event_operation, true); + .report_app_event(event_operation, true, cx); let app_state = Arc::new(AppState { languages: languages.clone(), From 5a43cbacbe95c5f610ba3ee5eb59520d218c9211 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 11:55:08 +0100 Subject: [PATCH 12/67] channels: Move name of the channel to the same line as the hash icon. --- crates/collab_ui/src/collab_panel/channel_modal.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index 114e89f1c4df54930b49c18235ead6f6fbec0af1..4955d01af26a61fcf9b89870b9a1a65a983026ce 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -164,8 +164,14 @@ impl Render for ChannelModal { .py_1() .rounded_t(px(8.)) .bg(cx.theme().colors().element_background) - .child(IconElement::new(Icon::Hash).size(IconSize::Medium)) - .child(Label::new(channel_name)) + .child( + h_stack() + .w_px() + .flex_1() + .gap_1() + .child(IconElement::new(Icon::Hash).size(IconSize::Medium)) + .child(Label::new(channel_name)), + ) .child( h_stack() .w_full() From f0afa3f9e3c6b4468ececcc06c29326a7e5cf733 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 4 Jan 2024 12:39:15 +0100 Subject: [PATCH 13/67] Show scrollbar even when buffer search highlights are outside viewport --- crates/editor/src/editor.rs | 6 ++++++ crates/editor/src/element.rs | 5 +++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1e537c7554dbd222d91d061717e455842d4eb666..cf074bad415d499516c1bb693cbfc19c4482f41c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8448,6 +8448,12 @@ impl Editor { }) } + pub fn has_background_highlights(&self) -> bool { + self.background_highlights + .get(&TypeId::of::()) + .map_or(false, |(_, highlights)| !highlights.is_empty()) + } + pub fn background_highlights_in_range( &self, search_range: Range, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 2b5c97bac5b1e067db5ff1640752c0bc8a9a489d..ab29f6c8b290de894b4528c50dd5f20bf5d748bf 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -8,6 +8,7 @@ use crate::{ hover_popover::{ self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, }, + items::BufferSearchHighlights, link_go_to_definition::{ go_to_fetched_definition, go_to_fetched_type_definition, show_link_definition, update_go_to_definition_link, update_inlay_link_and_hover_points, GoToDefinitionTrigger, @@ -1275,7 +1276,7 @@ impl EditorElement { let background_ranges = self .editor .read(cx) - .background_highlight_row_ranges::( + .background_highlight_row_ranges::( start_anchor..end_anchor, &layout.position_map.snapshot, 50000, @@ -1966,7 +1967,7 @@ impl EditorElement { (is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs()) || // Selections - (is_singleton && scrollbar_settings.selections && !highlighted_ranges.is_empty()) + (is_singleton && scrollbar_settings.selections && editor.has_background_highlights::()) // Scrollmanager || editor.scroll_manager.scrollbars_visible() } From f6af7ab27c612a1c041dd3ccdf582f765c9bb557 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 4 Jan 2024 15:00:36 +0100 Subject: [PATCH 14/67] Fix quitting Zed when project was unshared --- crates/workspace/src/workspace.rs | 28 +++++++++++++----------- crates/zed/src/main.rs | 2 +- crates/zed/src/zed.rs | 36 +++++++++++++------------------ 3 files changed, 31 insertions(+), 35 deletions(-) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 9e3e59b309cf9a59662586ce14445e5fe70d1d96..69e30a6ccb65c215cd5a59efcb6821549233378d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1095,19 +1095,21 @@ impl Workspace { } pub fn close_global(_: &CloseWindow, cx: &mut AppContext) { - cx.windows().iter().find(|window| { - window - .update(cx, |_, window| { - if window.is_window_active() { - //This can only get called when the window's project connection has been lost - //so we don't need to prompt the user for anything and instead just close the window - window.remove_window(); - true - } else { - false - } - }) - .unwrap_or(false) + cx.defer(|cx| { + cx.windows().iter().find(|window| { + window + .update(cx, |_, window| { + if window.is_window_active() { + //This can only get called when the window's project connection has been lost + //so we don't need to prompt the user for anything and instead just close the window + window.remove_window(); + true + } else { + false + } + }) + .unwrap_or(false) + }); }); } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 21e6c8fa216f5faaac67a1eb646389e20f6ca3eb..e0da81edc4ae17702b55a306bae3ec8b9d7a2bfd 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -144,6 +144,7 @@ fn main() { cx.set_global(client.clone()); + zed::init(cx); theme::init(theme::LoadThemes::All, cx); project::Project::init(&client, cx); client::init(&client, cx); @@ -158,7 +159,6 @@ fn main() { cx, ); assistant::init(cx); - // component_test::init(cx); cx.spawn(|_| watch_languages(fs.clone(), languages.clone())) .detach(); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index bcfdb848abba8eb60a2084a0dec571c70d83a0e6..fb85b1fc012085720dd7e11c1863cefcae84723a 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -64,6 +64,13 @@ actions!( ] ); +pub fn init(cx: &mut AppContext) { + cx.on_action(|_: &Hide, cx| cx.hide()); + cx.on_action(|_: &HideOthers, cx| cx.hide_other_apps()); + cx.on_action(|_: &ShowAll, cx| cx.unhide_other_apps()); + cx.on_action(quit); +} + pub fn build_window_options( bounds: Option, display_uuid: Option, @@ -130,7 +137,6 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { status_bar.add_left_item(diagnostic_summary, cx); status_bar.add_left_item(activity_indicator, cx); status_bar.add_right_item(feedback_button, cx); - // status_bar.add_right_item(copilot, cx); status_bar.add_right_item(copilot, cx); status_bar.add_right_item(active_buffer_language, cx); status_bar.add_right_item(vim_mode_indicator, cx); @@ -207,15 +213,6 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { workspace .register_action(about) - .register_action(|_, _: &Hide, cx| { - cx.hide(); - }) - .register_action(|_, _: &HideOthers, cx| { - cx.hide_other_apps(); - }) - .register_action(|_, _: &ShowAll, cx| { - cx.unhide_other_apps(); - }) .register_action(|_, _: &Minimize, cx| { cx.minimize_window(); }) @@ -225,7 +222,6 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { .register_action(|_, _: &ToggleFullScreen, cx| { cx.toggle_full_screen(); }) - .register_action(quit) .register_action(|_, action: &OpenZedURL, cx| { cx.global::>() .open_urls(&[action.url.clone()]) @@ -451,10 +447,10 @@ fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext) { .detach(); } -fn quit(_: &mut Workspace, _: &Quit, cx: &mut gpui::ViewContext) { +fn quit(_: &Quit, cx: &mut AppContext) { let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit; - cx.spawn(|_, mut cx| async move { - let mut workspace_windows = cx.update(|_, cx| { + cx.spawn(|mut cx| async move { + let mut workspace_windows = cx.update(|cx| { cx.windows() .into_iter() .filter_map(|window| window.downcast::()) @@ -463,14 +459,14 @@ fn quit(_: &mut Workspace, _: &Quit, cx: &mut gpui::ViewContext) { // If multiple windows have unsaved changes, and need a save prompt, // prompt in the active window before switching to a different window. - cx.update(|_, cx| { + cx.update(|cx| { workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false)); }) .log_err(); - if let (true, Some(_)) = (should_confirm, workspace_windows.first().copied()) { - let answer = cx - .update(|_, cx| { + if let (true, Some(workspace)) = (should_confirm, workspace_windows.first().copied()) { + let answer = workspace + .update(&mut cx, |_, cx| { cx.prompt( PromptLevel::Info, "Are you sure you want to quit?", @@ -500,9 +496,7 @@ fn quit(_: &mut Workspace, _: &Quit, cx: &mut gpui::ViewContext) { } } } - cx.update(|_, cx| { - cx.quit(); - })?; + cx.update(|cx| cx.quit())?; anyhow::Ok(()) }) .detach_and_log_err(cx); From b78497bf5433ecdcb8300c0d432ccc7f9c2a56eb Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 4 Jan 2024 15:12:25 +0100 Subject: [PATCH 15/67] Clip List items that partially overflow --- crates/gpui/src/elements/list.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 364b610eee761f8cd076c39aa1206c6d97016b0b..2a47a16741cf67c0cefb8a094d2f9e506cacbdf4 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -1,7 +1,7 @@ use crate::{ - point, px, AnyElement, AvailableSpace, BorrowAppContext, Bounds, DispatchPhase, Element, - IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, - WindowContext, + point, px, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, + DispatchPhase, Element, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, + StyleRefinement, Styled, WindowContext, }; use collections::VecDeque; use refineable::Refineable as _; @@ -317,7 +317,7 @@ impl Element for List { fn paint( &mut self, - bounds: crate::Bounds, + bounds: Bounds, _state: &mut Self::State, cx: &mut crate::WindowContext, ) { @@ -444,13 +444,15 @@ impl Element for List { new_items.append(cursor.suffix(&()), &()); // Paint the visible items - let mut item_origin = bounds.origin; - item_origin.y -= scroll_top.offset_in_item; - for item_element in &mut item_elements { - let item_height = item_element.measure(available_item_space, cx).height; - item_element.draw(item_origin, available_item_space, cx); - item_origin.y += item_height; - } + cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + let mut item_origin = bounds.origin; + item_origin.y -= scroll_top.offset_in_item; + for item_element in &mut item_elements { + let item_height = item_element.measure(available_item_space, cx).height; + item_element.draw(item_origin, available_item_space, cx); + item_origin.y += item_height; + } + }); state.items = new_items; state.last_layout_bounds = Some(bounds); From 5c32dd5688cd4e613b18890d926405276c0ad76d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 3 Jan 2024 22:05:35 -0700 Subject: [PATCH 16/67] Better TestWindow support --- Cargo.lock | 2 +- crates/gpui/src/app/test_context.rs | 132 ++++------------- crates/gpui/src/platform/test/platform.rs | 16 ++- crates/gpui/src/platform/test/window.rs | 165 ++++++++++++++++++---- 4 files changed, 171 insertions(+), 144 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d0af3c00876104d2f8554ee43ed757689bce281..13d4be62337ae0557fbc59538db4a4bdceacd22e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9514,7 +9514,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.119.0" +version = "0.120.0" dependencies = [ "activity_indicator", "ai", diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 44f303ac0bebfde1ded9c4deb1eebef40ad06633..dfbffaf2e59282f2878bf3aee7cea5aeac0d09fa 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -1,14 +1,13 @@ use crate::{ div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, - BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor, - InputEvent, IntoElement, KeyDownEvent, Keystroke, Model, ModelContext, Pixels, Platform, - PlatformWindow, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, - TestWindowHandlers, TextSystem, View, ViewContext, VisualContext, WindowBounds, WindowContext, - WindowHandle, WindowOptions, + BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor, + IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result, Size, Task, + TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext, + WindowContext, WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; -use std::{future::Future, mem, ops::Deref, rc::Rc, sync::Arc, time::Duration}; +use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration}; #[derive(Clone)] pub struct TestAppContext { @@ -185,42 +184,7 @@ impl TestAppContext { } pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size) { - let (mut handlers, scale_factor) = self - .app - .borrow_mut() - .update_window(window_handle, |_, cx| { - let platform_window = cx.window.platform_window.as_test().unwrap(); - let scale_factor = platform_window.scale_factor(); - match &mut platform_window.bounds { - WindowBounds::Fullscreen | WindowBounds::Maximized => { - platform_window.bounds = WindowBounds::Fixed(Bounds { - origin: Point::default(), - size: size.map(|pixels| f64::from(pixels).into()), - }); - } - WindowBounds::Fixed(bounds) => { - bounds.size = size.map(|pixels| f64::from(pixels).into()); - } - } - - ( - mem::take(&mut platform_window.handlers.lock().resize), - scale_factor, - ) - }) - .unwrap(); - - for handler in &mut handlers { - handler(size, scale_factor); - } - - self.app - .borrow_mut() - .update_window(window_handle, |_, cx| { - let platform_window = cx.window.platform_window.as_test().unwrap(); - platform_window.handlers.lock().resize = handlers; - }) - .unwrap(); + self.test_window(window_handle).simulate_resize(size); } pub fn spawn(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task @@ -313,41 +277,22 @@ impl TestAppContext { keystroke: Keystroke, is_held: bool, ) { - let keystroke2 = keystroke.clone(); - let handled = window - .update(self, |_, cx| { - cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held })) - }) - .is_ok_and(|handled| handled); - if handled { - return; - } - - let input_handler = self.update_test_window(window, |window| window.input_handler.clone()); - let Some(input_handler) = input_handler else { - panic!( - "dispatch_keystroke {:?} failed to dispatch action or input", - &keystroke2 - ); - }; - let text = keystroke2.ime_key.unwrap_or(keystroke2.key); - input_handler.lock().replace_text_in_range(None, &text); + self.test_window(window) + .simulate_keystroke(keystroke, is_held) } - pub fn update_test_window( - &mut self, - window: AnyWindowHandle, - f: impl FnOnce(&mut TestWindow) -> R, - ) -> R { - window - .update(self, |_, cx| { - f(cx.window - .platform_window - .as_any_mut() - .downcast_mut::() - .unwrap()) - }) + pub fn test_window(&self, window: AnyWindowHandle) -> TestWindow { + self.app + .borrow_mut() + .windows + .get_mut(window.id) + .unwrap() + .as_mut() .unwrap() + .platform_window + .as_test() + .unwrap() + .clone() } pub fn notifications(&mut self, entity: &impl Entity) -> impl Stream { @@ -563,11 +508,7 @@ impl<'a> VisualTestContext<'a> { } pub fn window_title(&mut self) -> Option { - self.cx - .update_window(self.window, |_, cx| { - cx.window.platform_window.as_test().unwrap().title.clone() - }) - .unwrap() + self.cx.test_window(self.window).0.lock().title.clone() } pub fn simulate_keystrokes(&mut self, keystrokes: &str) { @@ -579,36 +520,15 @@ impl<'a> VisualTestContext<'a> { } pub fn simulate_activation(&mut self) { - self.simulate_window_events(&mut |handlers| { - handlers - .active_status_change - .iter_mut() - .for_each(|f| f(true)); - }) + self.cx + .test_window(self.window) + .simulate_active_status_change(true) } pub fn simulate_deactivation(&mut self) { - self.simulate_window_events(&mut |handlers| { - handlers - .active_status_change - .iter_mut() - .for_each(|f| f(false)); - }) - } - - fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) { - let handlers = self - .cx - .update_window(self.window, |_, cx| { - cx.window - .platform_window - .as_test() - .unwrap() - .handlers - .clone() - }) - .unwrap(); - f(&mut *handlers.lock()); + self.cx + .test_window(self.window) + .simulate_active_status_change(false) } } diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index cc683cacb68f0e471ef9b1fa5596581d81d3d0f3..889e8b971e9232ad2d64baf01104758a4af6575a 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -19,7 +19,7 @@ pub struct TestPlatform { background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, - active_window: Arc>>, + pub(crate) active_window: RefCell>, active_display: Rc, active_cursor: Mutex, current_clipboard_item: Mutex>, @@ -106,7 +106,7 @@ impl Platform for TestPlatform { } fn activate(&self, _ignoring_other_apps: bool) { - unimplemented!() + // } fn hide(&self) { @@ -130,7 +130,10 @@ impl Platform for TestPlatform { } fn active_window(&self) -> Option { - self.active_window.lock().clone() + self.active_window + .borrow() + .as_ref() + .map(|window| window.0.lock().handle) } fn open_window( @@ -139,12 +142,13 @@ impl Platform for TestPlatform { options: WindowOptions, _draw: Box Result>, ) -> Box { - *self.active_window.lock() = Some(handle); - Box::new(TestWindow::new( + let window = TestWindow::new( options, + handle, self.weak.clone(), self.active_display.clone(), - )) + ); + Box::new(window) } fn set_display_link_output_callback( diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index 9df513d1f7ea46b0dffb452d27dbd82f5aba2176..eee2cf93c76515b176a811fc0f985f4b69dc7daa 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -1,7 +1,8 @@ use crate::{ - px, AtlasKey, AtlasTextureId, AtlasTile, Pixels, PlatformAtlas, PlatformDisplay, - PlatformInputHandler, PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance, - WindowBounds, WindowOptions, + px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, InputEvent, KeyDownEvent, + Keystroke, Pixels, Platform, PlatformAtlas, PlatformDisplay, PlatformInputHandler, + PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds, + WindowOptions, }; use collections::HashMap; use parking_lot::Mutex; @@ -10,51 +11,122 @@ use std::{ sync::{self, Arc}, }; -#[derive(Default)] -pub(crate) struct TestWindowHandlers { - pub(crate) active_status_change: Vec>, - pub(crate) input: Vec bool>>, - pub(crate) moved: Vec>, - pub(crate) resize: Vec, f32)>>, -} - -pub struct TestWindow { +pub struct TestWindowState { pub(crate) bounds: WindowBounds, + pub(crate) handle: AnyWindowHandle, display: Rc, pub(crate) title: Option, pub(crate) edited: bool, - pub(crate) input_handler: Option>>>, - pub(crate) handlers: Arc>, platform: Weak, sprite_atlas: Arc, + + input_callback: Option bool>>, + active_status_change_callback: Option>, + resize_callback: Option, f32)>>, + moved_callback: Option>, + input_handler: Option>, } +#[derive(Clone)] +pub struct TestWindow(pub(crate) Arc>); + impl TestWindow { pub fn new( options: WindowOptions, + handle: AnyWindowHandle, platform: Weak, display: Rc, ) -> Self { - Self { + Self(Arc::new(Mutex::new(TestWindowState { bounds: options.bounds, display, platform, - input_handler: None, + handle, sprite_atlas: Arc::new(TestAtlas::new()), - handlers: Default::default(), title: Default::default(), edited: false, + + input_callback: None, + active_status_change_callback: None, + resize_callback: None, + moved_callback: None, + input_handler: None, + }))) + } + + pub fn simulate_resize(&mut self, size: Size) { + let scale_factor = self.scale_factor(); + let mut lock = self.0.lock(); + let Some(mut callback) = lock.resize_callback.take() else { + return; + }; + match &mut lock.bounds { + WindowBounds::Fullscreen | WindowBounds::Maximized => { + lock.bounds = WindowBounds::Fixed(Bounds { + origin: Point::default(), + size: size.map(|pixels| f64::from(pixels).into()), + }); + } + WindowBounds::Fixed(bounds) => { + bounds.size = size.map(|pixels| f64::from(pixels).into()); + } } + drop(lock); + callback(size, scale_factor); + self.0.lock().resize_callback = Some(callback); + } + + pub fn simulate_active_status_change(&self, active: bool) { + let mut lock = self.0.lock(); + let Some(mut callback) = lock.active_status_change_callback.take() else { + return; + }; + drop(lock); + callback(active); + self.0.lock().active_status_change_callback = Some(callback); + } + + pub fn simulate_input(&mut self, event: InputEvent) -> bool { + let mut lock = self.0.lock(); + let Some(mut callback) = lock.input_callback.take() else { + return false; + }; + drop(lock); + let result = callback(event); + self.0.lock().input_callback = Some(callback); + result + } + + pub fn simulate_keystroke(&mut self, keystroke: Keystroke, is_held: bool) { + if self.simulate_input(InputEvent::KeyDown(KeyDownEvent { + keystroke: keystroke.clone(), + is_held, + })) { + return; + } + + let mut lock = self.0.lock(); + let Some(mut input_handler) = lock.input_handler.take() else { + panic!( + "simulate_keystroke {:?} input event was not handled and there was no active input", + &keystroke + ); + }; + drop(lock); + let text = keystroke.ime_key.unwrap_or(keystroke.key); + input_handler.replace_text_in_range(None, &text); + + self.0.lock().input_handler = Some(input_handler); } } impl PlatformWindow for TestWindow { fn bounds(&self) -> WindowBounds { - self.bounds + self.0.lock().bounds } fn content_size(&self) -> Size { - let bounds = match self.bounds { + let bounds = match self.bounds() { WindowBounds::Fixed(bounds) => bounds, WindowBounds::Maximized | WindowBounds::Fullscreen => self.display().bounds(), }; @@ -74,7 +146,7 @@ impl PlatformWindow for TestWindow { } fn display(&self) -> std::rc::Rc { - self.display.clone() + self.0.lock().display.clone() } fn mouse_position(&self) -> Point { @@ -90,11 +162,11 @@ impl PlatformWindow for TestWindow { } fn set_input_handler(&mut self, input_handler: Box) { - self.input_handler = Some(Arc::new(Mutex::new(input_handler))); + self.0.lock().input_handler = Some(input_handler); } fn clear_input_handler(&mut self) { - self.input_handler = None; + self.0.lock().input_handler = None; } fn prompt( @@ -103,19 +175,50 @@ impl PlatformWindow for TestWindow { _msg: &str, _answers: &[&str], ) -> futures::channel::oneshot::Receiver { - self.platform.upgrade().expect("platform dropped").prompt() + self.0 + .lock() + .platform + .upgrade() + .expect("platform dropped") + .prompt() } fn activate(&self) { - unimplemented!() + let this = self.clone(); + let executor = self + .0 + .lock() + .platform + .upgrade() + .unwrap() + .foreground_executor() + .clone(); + + executor + .spawn(async move { + let state = this.0.lock(); + let platform = state.platform.upgrade().unwrap(); + let previous_window = platform.active_window.borrow_mut().replace(this.clone()); + drop(state); + drop(platform); + if let Some(previous_window) = previous_window { + if Arc::ptr_eq(&previous_window.0, &this.0) { + return; + } + previous_window.simulate_active_status_change(false); + } + + this.simulate_active_status_change(true); + }) + .detach(); } fn set_title(&mut self, title: &str) { - self.title = Some(title.to_owned()); + self.0.lock().title = Some(title.to_owned()); } fn set_edited(&mut self, edited: bool) { - self.edited = edited; + self.0.lock().edited = edited; } fn show_character_palette(&self) { @@ -135,15 +238,15 @@ impl PlatformWindow for TestWindow { } fn on_input(&self, callback: Box bool>) { - self.handlers.lock().input.push(callback) + self.0.lock().input_callback = Some(callback) } fn on_active_status_change(&self, callback: Box) { - self.handlers.lock().active_status_change.push(callback) + self.0.lock().active_status_change_callback = Some(callback) } fn on_resize(&self, callback: Box, f32)>) { - self.handlers.lock().resize.push(callback) + self.0.lock().resize_callback = Some(callback) } fn on_fullscreen(&self, _callback: Box) { @@ -151,7 +254,7 @@ impl PlatformWindow for TestWindow { } fn on_moved(&self, callback: Box) { - self.handlers.lock().moved.push(callback) + self.0.lock().moved_callback = Some(callback) } fn on_should_close(&self, _callback: Box bool>) { @@ -175,7 +278,7 @@ impl PlatformWindow for TestWindow { } fn sprite_atlas(&self) -> sync::Arc { - self.sprite_atlas.clone() + self.0.lock().sprite_atlas.clone() } fn as_test(&mut self) -> Option<&mut TestWindow> { From 3ab20626147a0bbe4739e6bdb8faf8ee78137b22 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 4 Jan 2024 16:15:32 +0100 Subject: [PATCH 17/67] Fix border rendering After implementing it a while ago, our previous interpolation scheme didn't really make sense to me and was causing borders to be rendered incorrectly. We don't really draw backgrounds and borders as part of the same draw call anymore, but it seemed reasonable to have a correct implementation in the shader anyway. This commit uses Porter-Duff compositing (i.e., `over`) to produce a color that is the result of superimposing the border on top of the background. Then, we linearly interpolate towards the background color as we slide out of the border and into the background. --- crates/gpui/src/platform/mac/shaders.metal | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/gpui/src/platform/mac/shaders.metal b/crates/gpui/src/platform/mac/shaders.metal index aba01b9d5b059da1c1df55c0a01120d8be10775b..264fa55134bb39db7c45541434a4126afac87116 100644 --- a/crates/gpui/src/platform/mac/shaders.metal +++ b/crates/gpui/src/platform/mac/shaders.metal @@ -16,6 +16,7 @@ float gaussian(float x, float sigma); float2 erf(float2 x); float blur_along_x(float x, float y, float sigma, float corner, float2 half_size); +float4 over(float4 below, float4 above); struct QuadVertexOutput { float4 position [[position]]; @@ -108,21 +109,11 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]], color = input.background_color; } else { float inset_distance = distance + border_width; - - // Decrease border's opacity as we move inside the background. - input.border_color.a *= 1. - saturate(0.5 - inset_distance); - - // Alpha-blend the border and the background. - float output_alpha = input.border_color.a + - input.background_color.a * (1. - input.border_color.a); - float3 premultiplied_border_rgb = - input.border_color.rgb * input.border_color.a; - float3 premultiplied_background_rgb = - input.background_color.rgb * input.background_color.a; - float3 premultiplied_output_rgb = - premultiplied_border_rgb + - premultiplied_background_rgb * (1. - input.border_color.a); - color = float4(premultiplied_output_rgb, output_alpha); + // Blend the border on top of the background and then linearly interpolate + // between the two as we slide inside the background. + float4 blended_border = over(input.background_color, input.border_color); + color = mix(blended_border, input.background_color, + saturate(0.5 - inset_distance)); } return color * float4(1., 1., 1., saturate(0.5 - distance)); @@ -653,3 +644,12 @@ float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds, position.y - clip_bounds.origin.y, clip_bounds.origin.y + clip_bounds.size.height - position.y); } + +float4 over(float4 below, float4 above) { + float4 result; + float alpha = above.a + below.a * (1.0 - above.a); + result.rgb = + (above.rgb * above.a + below.rgb * below.a * (1.0 - above.a)) / alpha; + result.a = alpha; + return result; +} From 5a1509ef269b608f95d7f73355f533ead852de72 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 4 Jan 2024 16:37:13 +0100 Subject: [PATCH 18/67] Re-enable key bindings for `AssistantPanel` --- crates/assistant/src/assistant_panel.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 3625b64e32ee52d1efad6d4c043c5147b2777ffc..9221d87f60fda92990997a1401bf54325a099c6b 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -1157,6 +1157,7 @@ impl Render for AssistantPanel { }); v_stack() + .key_context("AssistantPanel") .size_full() .on_action(cx.listener(|this, _: &workspace::NewFile, cx| { this.new_conversation(cx); From 9d146a2a6f75c4c59f0e494c76196016399b61da Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 4 Jan 2024 08:59:31 -0700 Subject: [PATCH 19/67] Fix vim tests --- crates/workspace/src/workspace.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 76715f69bef9ffb5e7f4ced25d00372df897b7e5..6c31366f93c663cfe8c0cd0bd5b4f9ad82ce023e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3264,6 +3264,7 @@ impl Workspace { let user_store = project.read(cx).user_store(); let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx)); + cx.activate_window(); let app_state = Arc::new(AppState { languages: project.read(cx).languages().clone(), workspace_store, From a7550de8c57c4ee143ed696de3362314f0c3b305 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 3 Jan 2024 14:36:10 -0500 Subject: [PATCH 20/67] Show pointer hand on tab & give last tab border right --- crates/ui/src/components/tab.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ui/src/components/tab.rs b/crates/ui/src/components/tab.rs index 9bafb0c0bba7c8219c729b5b3e15830355710fdf..351c851bb9088db8907e7defff71fe21d79ffe75 100644 --- a/crates/ui/src/components/tab.rs +++ b/crates/ui/src/components/tab.rs @@ -126,13 +126,14 @@ impl RenderOnce for Tab { if self.selected { this.border_l().border_r().pb_px() } else { - this.pr_px().pl_px().border_b() + this.pr_px().pl_px().border_b().border_r() } } TabPosition::Middle(Ordering::Equal) => this.border_l().border_r().pb_px(), TabPosition::Middle(Ordering::Less) => this.border_l().pr_px().border_b(), TabPosition::Middle(Ordering::Greater) => this.border_r().pl_px().border_b(), }) + .cursor_pointer() .child( h_stack() .group("") From 6f140c8ae3582b4480ffc8345acb0b0120016432 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 4 Jan 2024 11:15:53 -0500 Subject: [PATCH 21/67] WIP tinted buttons --- .../ui/src/components/button/button_like.rs | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index f255104432816f090aad741fdcb5800d589ff74b..47aae89dfd983df876bcf7bfe4d86c656ed73f18 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -36,17 +36,43 @@ pub enum IconPosition { End, } +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] +pub enum TintColor { + #[default] + Accent, + Negative, + Positive, + Warning, +} + +impl TintColor { + fn button_style(self, cx: &mut WindowContext) -> ButtonLikeStyles { + match self { + TintColor::Accent => ButtonLikeStyles { + background: cx.theme().status().info_background, + border_color: cx.theme().status().info_border, + label_color: cx.theme().colors().text, + icon_color: cx.theme().colors().text, + }, + _ => ButtonLikeStyles { + background: gpui::red(), + border_color: gpui::red(), + label_color: gpui::red(), + icon_color: gpui::red(), + }, + } + } +} + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum ButtonStyle { /// A filled button with a solid background color. Provides emphasis versus /// the more common subtle button. Filled, - /// 🚧 Under construction 🚧 - /// /// Used to emphasize a button in some way, like a selected state, or a semantic /// coloring like an error or success button. - Tinted, + Tinted(TintColor), /// The default button style, used for most buttons. Has a transparent background, /// but has a background color to indicate states like hover and active. @@ -86,7 +112,9 @@ impl ButtonStyle { label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle::Tinted => ButtonLikeStyles { + ButtonStyle::Tinted(TintColor::Accent) => TintColor::Accent.button_style(cx), + // TODO: Finish tint colors + ButtonStyle::Tinted(_) => ButtonLikeStyles { background: gpui::red(), border_color: gpui::red(), label_color: gpui::red(), @@ -115,7 +143,9 @@ impl ButtonStyle { label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle::Tinted => ButtonLikeStyles { + ButtonStyle::Tinted(TintColor::Accent) => TintColor::Accent.button_style(cx), + // TODO: Finish tint colors + ButtonStyle::Tinted(_) => ButtonLikeStyles { background: gpui::red(), border_color: gpui::red(), label_color: gpui::red(), @@ -146,7 +176,9 @@ impl ButtonStyle { label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle::Tinted => ButtonLikeStyles { + ButtonStyle::Tinted(TintColor::Accent) => TintColor::Accent.button_style(cx), + // TODO: Finish tint colors + ButtonStyle::Tinted(_) => ButtonLikeStyles { background: gpui::red(), border_color: gpui::red(), label_color: gpui::red(), @@ -178,7 +210,9 @@ impl ButtonStyle { label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle::Tinted => ButtonLikeStyles { + ButtonStyle::Tinted(TintColor::Accent) => TintColor::Accent.button_style(cx), + // TODO: Finish tint colors + ButtonStyle::Tinted(_) => ButtonLikeStyles { background: gpui::red(), border_color: gpui::red(), label_color: gpui::red(), @@ -208,7 +242,9 @@ impl ButtonStyle { label_color: Color::Disabled.color(cx), icon_color: Color::Disabled.color(cx), }, - ButtonStyle::Tinted => ButtonLikeStyles { + ButtonStyle::Tinted(TintColor::Accent) => TintColor::Accent.button_style(cx), + // TODO: Finish tint colors + ButtonStyle::Tinted(_) => ButtonLikeStyles { background: gpui::red(), border_color: gpui::red(), label_color: gpui::red(), From 6f4a08ba5a96a13793449d094dfd78a34b237440 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 4 Jan 2024 17:25:02 +0100 Subject: [PATCH 22/67] Prevent scrolling editor and resizing panels at the same time This fixes a bug that would cause Zed to never stop resizing panels when the drag handle overlapped with an editor scrollbar. Co-Authored-By: Marshall --- crates/editor/src/element.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ab29f6c8b290de894b4528c50dd5f20bf5d748bf..b1a3e73d6a6372c99551a409edc630331727523c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1230,6 +1230,14 @@ impl EditorElement { return; } + // If a drag took place after we started dragging the scrollbar, + // cancel the scrollbar drag. + if cx.has_active_drag() { + self.editor.update(cx, |editor, cx| { + editor.scroll_manager.set_is_dragging_scrollbar(false, cx); + }); + } + let top = bounds.origin.y; let bottom = bounds.lower_left().y; let right = bounds.lower_right().x; From 4e310b99aa0da7d5d231dbacc398222aa297e760 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 4 Jan 2024 09:45:39 -0700 Subject: [PATCH 23/67] Implement "open in terminal" --- Cargo.lock | 2 +- crates/project_panel/src/project_panel.rs | 29 ++++++++--------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d0af3c00876104d2f8554ee43ed757689bce281..13d4be62337ae0557fbc59538db4a4bdceacd22e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9514,7 +9514,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.119.0" +version = "0.120.0" dependencies = [ "activity_indicator", "ai", diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 6f438098b718a72602e69b81339129e00fa8a3ba..e0bee14df27479021634d3400b13850d44fa51a9 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -971,25 +971,16 @@ impl ProjectPanel { } } - fn open_in_terminal(&mut self, _: &OpenInTerminal, _cx: &mut ViewContext) { - todo!() - // if let Some((worktree, entry)) = self.selected_entry(cx) { - // let window = cx.window(); - // let view_id = cx.view_id(); - // let path = worktree.abs_path().join(&entry.path); - - // cx.app_context() - // .spawn(|mut cx| async move { - // window.dispatch_action( - // view_id, - // &workspace::OpenTerminal { - // working_directory: path, - // }, - // &mut cx, - // ); - // }) - // .detach(); - // } + fn open_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext) { + if let Some((worktree, entry)) = self.selected_entry(cx) { + let path = worktree.abs_path().join(&entry.path); + cx.dispatch_action( + workspace::OpenTerminal { + working_directory: path, + } + .boxed_clone(), + ) + } } pub fn new_search_in_directory( From d79b8e4b98313ac418aee5100dd89522f9169d06 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 4 Jan 2024 12:35:51 -0500 Subject: [PATCH 24/67] Fix `SendText`/`SendKeystroke` having the wrong context name in terminal Co-Authored-By: Max Brunsfeld --- crates/terminal/src/terminal.rs | 10 +--------- crates/terminal_view/src/terminal_view.rs | 2 +- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index b15bd7c6d659cc7794b770934e95775c0ae41507..af922f109e9ed218fc56c950cae8bc2acbc0900d 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -61,15 +61,7 @@ use lazy_static::lazy_static; actions!( terminal, - [ - Clear, - Copy, - Paste, - ShowCharacterPalette, - SearchTest, - SendText, - SendKeystroke, - ] + [Clear, Copy, Paste, ShowCharacterPalette, SearchTest,] ); ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index d4dea29b49e71e66604ec56799d166778792ae9a..48765b67c0117745123081b44b29651e54364168 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -57,7 +57,7 @@ pub struct SendText(String); #[derive(Clone, Debug, Default, Deserialize, PartialEq)] pub struct SendKeystroke(String); -impl_actions!(terminal_view, [SendText, SendKeystroke]); +impl_actions!(terminal, [SendText, SendKeystroke]); pub fn init(cx: &mut AppContext) { terminal_panel::init(cx); From 90fc1ebaf6acfac4d8b3ab8e18871216335c1a01 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jan 2024 09:53:57 -0800 Subject: [PATCH 25/67] Fix version comparison in auto update Co-authored-by: Antonio Scandurra --- crates/auto_update/src/auto_update.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 691b83479f1e4fb793068aca9d4f2af55124413a..a2a90d4f2f69c94fc87a56ff13d8aa861304699b 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -270,7 +270,7 @@ impl AutoUpdater { ReleaseChannel::Nightly => cx .try_read_global::(|sha, _| release.version != sha.0) .unwrap_or(true), - _ => release.version.parse::()? <= current_version, + _ => release.version.parse::()? > current_version, }; if !should_download { From 3d1023ef52a4041c355af370e929eab713c5c3a7 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 19:24:22 +0100 Subject: [PATCH 26/67] lsp: Do not cache initialization options --- crates/language/src/language.rs | 3 --- crates/project/src/project.rs | 26 ++++++++++++-------------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 5564481c6bdc07a41d0b3fdd91a75970de9b8e21..366d2b0098ca36437252044c50825bceaed96081 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -113,7 +113,6 @@ pub struct LanguageServerName(pub Arc); pub struct CachedLspAdapter { pub name: LanguageServerName, pub short_name: &'static str, - pub initialization_options: Option, pub disk_based_diagnostic_sources: Vec, pub disk_based_diagnostics_progress_token: Option, pub language_ids: HashMap, @@ -125,7 +124,6 @@ impl CachedLspAdapter { pub async fn new(adapter: Arc) -> Arc { let name = adapter.name().await; let short_name = adapter.short_name(); - let initialization_options = adapter.initialization_options().await; let disk_based_diagnostic_sources = adapter.disk_based_diagnostic_sources().await; let disk_based_diagnostics_progress_token = adapter.disk_based_diagnostics_progress_token().await; @@ -134,7 +132,6 @@ impl CachedLspAdapter { Arc::new(CachedLspAdapter { name, short_name, - initialization_options, disk_based_diagnostic_sources, disk_based_diagnostics_progress_token, language_ids, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index b9c73ae67785d48d7912414113329bb4a6d2e0da..a513b3907adcee142db6c09b481c99ae03384b6d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2816,15 +2816,6 @@ impl Project { let lsp = project_settings.lsp.get(&adapter.name.0); let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); - let mut initialization_options = adapter.initialization_options.clone(); - match (&mut initialization_options, override_options) { - (Some(initialization_options), Some(override_options)) => { - merge_json_value_into(override_options, initialization_options); - } - (None, override_options) => initialization_options = override_options, - _ => {} - } - let server_id = pending_server.server_id; let container_dir = pending_server.container_dir.clone(); let state = LanguageServerState::Starting({ @@ -2837,7 +2828,7 @@ impl Project { let result = Self::setup_and_insert_language_server( this.clone(), &worktree_path, - initialization_options, + override_options, pending_server, adapter.clone(), language.clone(), @@ -2958,7 +2949,7 @@ impl Project { async fn setup_and_insert_language_server( this: WeakModel, worktree_path: &Path, - initialization_options: Option, + override_initialization_options: Option, pending_server: PendingLanguageServer, adapter: Arc, language: Arc, @@ -2968,7 +2959,7 @@ impl Project { ) -> Result>> { let language_server = Self::setup_pending_language_server( this.clone(), - initialization_options, + override_initialization_options, pending_server, worktree_path, adapter.clone(), @@ -2998,7 +2989,7 @@ impl Project { async fn setup_pending_language_server( this: WeakModel, - initialization_options: Option, + override_options: Option, pending_server: PendingLanguageServer, worktree_path: &Path, adapter: Arc, @@ -3164,7 +3155,14 @@ impl Project { } }) .detach(); - + let mut initialization_options = adapter.adapter.initialization_options().await; + match (&mut initialization_options, override_options) { + (Some(initialization_options), Some(override_options)) => { + merge_json_value_into(override_options, initialization_options); + } + (None, override_options) => initialization_options = override_options, + _ => {} + } let language_server = language_server.initialize(initialization_options).await?; language_server From 09b32e6a0e364fbbb29ede470596825ab4b13376 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 4 Jan 2024 14:01:01 -0500 Subject: [PATCH 27/67] Attempt to run keystroke actions before attempting key listeners --- crates/gpui/src/window.rs | 46 ++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 7b6541c937edaacdfff8f01f7ffe0386d74c1ba4..71e6cb9e559a97634ee7d2df8610569838c32a5d 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1583,36 +1583,16 @@ impl<'a> WindowContext<'a> { let mut actions: Vec> = Vec::new(); - // Capture phase let mut context_stack: SmallVec<[KeyContext; 16]> = SmallVec::new(); - self.propagate_event = true; - for node_id in &dispatch_path { let node = self.window.rendered_frame.dispatch_tree.node(*node_id); if let Some(context) = node.context.clone() { context_stack.push(context); } - - for key_listener in node.key_listeners.clone() { - key_listener(event, DispatchPhase::Capture, self); - if !self.propagate_event { - return; - } - } } - // Bubble phase for node_id in dispatch_path.iter().rev() { - // Handle low level key events - let node = self.window.rendered_frame.dispatch_tree.node(*node_id); - for key_listener in node.key_listeners.clone() { - key_listener(event, DispatchPhase::Bubble, self); - if !self.propagate_event { - return; - } - } - // Match keystrokes let node = self.window.rendered_frame.dispatch_tree.node(*node_id); if node.context.is_some() { @@ -1633,6 +1613,7 @@ impl<'a> WindowContext<'a> { self.clear_pending_keystrokes(); } + self.propagate_event = true; for action in actions { self.dispatch_action_on_node(node_id, action.boxed_clone()); if !self.propagate_event { @@ -1640,6 +1621,31 @@ impl<'a> WindowContext<'a> { return; } } + + // Capture phase + for node_id in &dispatch_path { + let node = self.window.rendered_frame.dispatch_tree.node(*node_id); + + for key_listener in node.key_listeners.clone() { + key_listener(event, DispatchPhase::Capture, self); + if !self.propagate_event { + return; + } + } + } + + // Bubble phase + for node_id in dispatch_path.iter().rev() { + // Handle low level key events + let node = self.window.rendered_frame.dispatch_tree.node(*node_id); + for key_listener in node.key_listeners.clone() { + key_listener(event, DispatchPhase::Bubble, self); + if !self.propagate_event { + return; + } + } + } + self.dispatch_keystroke_observers(event, None); } From e4aa7ba4f262eb794b798107efbae04fbecb2d87 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 4 Jan 2024 14:10:46 -0500 Subject: [PATCH 28/67] Try to load fallback fonts instead of panicking when a font is not found (#3891) This PR adjusts our font resolution code to attempt to use a fallback font if the specified font cannot be found. Right now our fallback font stack is `Zed Mono`, followed by `Helvetica` (in practice we should always be able to resolve `Zed Mono` since we bundle it with the app). In the future we'll want to surface the ability to set the fallback font stack from GPUI consumers, and potentially even support specifying font stacks in the user settings (as opposed to a single font family). Release Notes: - Fixed a panic when trying to load a font that could not be found. --- crates/editor/src/editor.rs | 2 +- crates/editor/src/element.rs | 4 +- crates/gpui/src/text_system.rs | 39 +++++++++++++++++++- crates/terminal_view/src/terminal_element.rs | 2 +- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index cf074bad415d499516c1bb693cbfc19c4482f41c..b53fda335ed55ad1d31885cbe227d9c9cf339c28 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9565,7 +9565,7 @@ impl InputHandler for Editor { ) -> Option> { let text_layout_details = self.text_layout_details(cx); let style = &text_layout_details.editor_style; - let font_id = cx.text_system().font_id(&style.text.font()).unwrap(); + let font_id = cx.text_system().resolve_font(&style.text.font()); let font_size = style.text.font_size.to_pixels(cx.rem_size()); let line_height = style.text.line_height_in_pixels(cx.rem_size()); let em_width = cx diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b1a3e73d6a6372c99551a409edc630331727523c..76a5d1ec5ce9131dfaeeba46c73e95e5181003e2 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1775,7 +1775,7 @@ impl EditorElement { let snapshot = editor.snapshot(cx); let style = self.style.clone(); - let font_id = cx.text_system().font_id(&style.text.font()).unwrap(); + let font_id = cx.text_system().resolve_font(&style.text.font()); let font_size = style.text.font_size.to_pixels(cx.rem_size()); let line_height = style.text.line_height_in_pixels(cx.rem_size()); let em_width = cx @@ -3782,7 +3782,7 @@ fn compute_auto_height_layout( } let style = editor.style.as_ref().unwrap(); - let font_id = cx.text_system().font_id(&style.text.font()).unwrap(); + let font_id = cx.text_system().resolve_font(&style.text.font()); let font_size = style.text.font_size.to_pixels(cx.rem_size()); let line_height = style.text.line_height_in_pixels(cx.rem_size()); let em_width = cx diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 944a9b78bec2a33b4b20e4e1e1b991565d42212b..60934b3959722eb76f9a86a16b6669d06109dd74 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -15,8 +15,9 @@ use crate::{ use anyhow::anyhow; use collections::FxHashMap; use core::fmt; +use itertools::Itertools; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; use std::{ cmp, fmt::{Debug, Display, Formatter}, @@ -42,6 +43,7 @@ pub struct TextSystem { raster_bounds: RwLock>>, wrapper_pool: Mutex>>, font_runs_pool: Mutex>>, + fallback_font_stack: SmallVec<[Font; 2]>, } impl TextSystem { @@ -54,6 +56,12 @@ impl TextSystem { font_ids_by_font: RwLock::default(), wrapper_pool: Mutex::default(), font_runs_pool: Mutex::default(), + fallback_font_stack: smallvec![ + // TODO: This is currently Zed-specific. + // We should allow GPUI users to provide their own fallback font stack. + font("Zed Mono"), + font("Helvetica") + ], } } @@ -72,6 +80,33 @@ impl TextSystem { } } + /// Resolves the specified font, falling back to the default font stack if + /// the font fails to load. + /// + /// # Panics + /// + /// Panics if the font and none of the fallbacks can be resolved. + pub fn resolve_font(&self, font: &Font) -> FontId { + if let Ok(font_id) = self.font_id(font) { + return font_id; + } + + for fallback in &self.fallback_font_stack { + if let Ok(font_id) = self.font_id(fallback) { + return font_id; + } + } + + panic!( + "failed to resolve font '{}' or any of the fallbacks: {}", + font.family, + self.fallback_font_stack + .iter() + .map(|fallback| &fallback.family) + .join(", ") + ); + } + pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Bounds { self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size)) } @@ -159,7 +194,7 @@ impl TextSystem { ) -> Result> { let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default(); for run in runs.iter() { - let font_id = self.font_id(&run.font)?; + let font_id = self.resolve_font(&run.font); if let Some(last_run) = font_runs.last_mut() { if last_run.font_id == font_id { last_run.len += run.len; diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 8be10f9469fd8a2fb716f469c39bae841500920e..328a6a1c4e8cd37dcc698ada592a60e7c9767628 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -421,7 +421,7 @@ impl TerminalElement { let rem_size = cx.rem_size(); let font_pixels = text_style.font_size.to_pixels(rem_size); let line_height = font_pixels * line_height.to_pixels(rem_size); - let font_id = cx.text_system().font_id(&text_style.font()).unwrap(); + let font_id = cx.text_system().resolve_font(&text_style.font()); // todo!(do we need to keep this unwrap?) let cell_width = text_system From 4dbec66cddf6c55e494ff0029e74980238faaa82 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 4 Jan 2024 21:13:51 +0200 Subject: [PATCH 29/67] Return back old project search behavior as default. Add a `workspace::DeploySearch` action and use it as a default for "cmd-shift-f" binding. This action opens existing search tab if it exists, or creates a new one otherwise. `workspace::NewSearch` action is still available and always opens an existing search tab. --- assets/keymaps/default.json | 2 +- crates/search/src/project_search.rs | 304 ++++++++++++++++++++++++++-- crates/workspace/src/workspace.rs | 1 + 3 files changed, 288 insertions(+), 19 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 5d700006393ea27a4e46348f08866ab3197f8215..3ff0db1a160e4d420a7aad83d56404e313ad3b46 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -402,7 +402,7 @@ "cmd-r": "workspace::ToggleRightDock", "cmd-j": "workspace::ToggleBottomDock", "alt-cmd-y": "workspace::CloseAllDocks", - "cmd-shift-f": "workspace::NewSearch", + "cmd-shift-f": "workspace::DeploySearch", "cmd-k cmd-t": "theme_selector::Toggle", "cmd-k cmd-s": "zed::OpenKeymap", "cmd-t": "project_symbols::Toggle", diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 9a91d619a43cf2cd4e9c024006494f2aa9b19989..b6523bc3cda6e5c8e97b9c9d4d34a96db157e34d 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -61,12 +61,12 @@ struct ActiveSearches(HashMap, WeakView>); struct ActiveSettings(HashMap, ProjectSearchSettings>); pub fn init(cx: &mut AppContext) { - // todo!() po cx.set_global(ActiveSearches::default()); cx.set_global(ActiveSettings::default()); cx.observe_new_views(|workspace: &mut Workspace, _cx| { workspace - .register_action(ProjectSearchView::deploy) + .register_action(ProjectSearchView::new_search) + .register_action(ProjectSearchView::deploy_search) .register_action(ProjectSearchBar::search_in_new); }) .detach(); @@ -941,11 +941,41 @@ impl ProjectSearchView { }); } + // Re-activate the most recently activated search or the most recent if it has been closed. + // If no search exists in the workspace, create a new one. + fn deploy_search( + workspace: &mut Workspace, + _: &workspace::DeploySearch, + cx: &mut ViewContext, + ) { + let active_search = cx + .global::() + .0 + .get(&workspace.project().downgrade()); + let existing = active_search + .and_then(|active_search| { + workspace + .items_of_type::(cx) + .filter(|search| &search.downgrade() == active_search) + .last() + }) + .or_else(|| workspace.item_of_type::(cx)); + Self::existing_or_new_search(workspace, existing, cx) + } + // Add another search tab to the workspace. - fn deploy( + fn new_search( workspace: &mut Workspace, _: &workspace::NewSearch, cx: &mut ViewContext, + ) { + Self::existing_or_new_search(workspace, None, cx) + } + + fn existing_or_new_search( + workspace: &mut Workspace, + existing: Option>, + cx: &mut ViewContext, ) { // Clean up entries for dropped projects cx.update_global(|state: &mut ActiveSearches, _cx| { @@ -962,19 +992,27 @@ impl ProjectSearchView { } }); - let settings = cx - .global::() - .0 - .get(&workspace.project().downgrade()); - - let settings = if let Some(settings) = settings { - Some(settings.clone()) + let search = if let Some(existing) = existing { + workspace.activate_item(&existing, cx); + existing } else { - None - }; + let settings = cx + .global::() + .0 + .get(&workspace.project().downgrade()); - let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx)); - let search = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings)); + let settings = if let Some(settings) = settings { + Some(settings.clone()) + } else { + None + }; + + let model = cx.new_model(|cx| ProjectSearch::new(workspace.project().clone(), cx)); + let view = cx.new_view(|cx| ProjectSearchView::new(model, cx, settings)); + + workspace.add_item(Box::new(view.clone()), cx); + view + }; workspace.add_item(Box::new(search.clone()), cx); @@ -2060,7 +2098,237 @@ pub mod tests { } #[gpui::test] - async fn test_project_search_focus(cx: &mut TestAppContext) { + async fn test_deploy_project_search_focus(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.background_executor.clone()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": "const ONE: usize = 1;", + "two.rs": "const TWO: usize = one::ONE + one::ONE;", + "three.rs": "const THREE: usize = one::ONE + two::TWO;", + "four.rs": "const FOUR: usize = one::ONE + three::THREE;", + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + let window = cx.add_window(|cx| Workspace::test_new(project, cx)); + let workspace = window.clone(); + let search_bar = window.build_view(cx, |_| ProjectSearchBar::new()); + + let active_item = cx.read(|cx| { + workspace + .read(cx) + .unwrap() + .active_pane() + .read(cx) + .active_item() + .and_then(|item| item.downcast::()) + }); + assert!( + active_item.is_none(), + "Expected no search panel to be active" + ); + + window + .update(cx, move |workspace, cx| { + assert_eq!(workspace.panes().len(), 1); + workspace.panes()[0].update(cx, move |pane, cx| { + pane.toolbar() + .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) + }); + + ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch, cx) + }) + .unwrap(); + + let Some(search_view) = cx.read(|cx| { + workspace + .read(cx) + .unwrap() + .active_pane() + .read(cx) + .active_item() + .and_then(|item| item.downcast::()) + }) else { + panic!("Search view expected to appear after new search event trigger") + }; + + cx.spawn(|mut cx| async move { + window + .update(&mut cx, |_, cx| { + cx.dispatch_action(ToggleFocus.boxed_clone()) + }) + .unwrap(); + }) + .detach(); + cx.background_executor.run_until_parked(); + window + .update(cx, |_, cx| { + search_view.update(cx, |search_view, cx| { + assert!( + search_view.query_editor.focus_handle(cx).is_focused(cx), + "Empty search view should be focused after the toggle focus event: no results panel to focus on", + ); + }); + }).unwrap(); + + window + .update(cx, |_, cx| { + search_view.update(cx, |search_view, cx| { + let query_editor = &search_view.query_editor; + assert!( + query_editor.focus_handle(cx).is_focused(cx), + "Search view should be focused after the new search view is activated", + ); + let query_text = query_editor.read(cx).text(cx); + assert!( + query_text.is_empty(), + "New search query should be empty but got '{query_text}'", + ); + let results_text = search_view + .results_editor + .update(cx, |editor, cx| editor.display_text(cx)); + assert!( + results_text.is_empty(), + "Empty search view should have no results but got '{results_text}'" + ); + }); + }) + .unwrap(); + + window + .update(cx, |_, cx| { + search_view.update(cx, |search_view, cx| { + search_view.query_editor.update(cx, |query_editor, cx| { + query_editor.set_text("sOMETHINGtHATsURELYdOESnOTeXIST", cx) + }); + search_view.search(cx); + }); + }) + .unwrap(); + cx.background_executor.run_until_parked(); + window + .update(cx, |_, cx| { + search_view.update(cx, |search_view, cx| { + let results_text = search_view + .results_editor + .update(cx, |editor, cx| editor.display_text(cx)); + assert!( + results_text.is_empty(), + "Search view for mismatching query should have no results but got '{results_text}'" + ); + assert!( + search_view.query_editor.focus_handle(cx).is_focused(cx), + "Search view should be focused after mismatching query had been used in search", + ); + }); + }).unwrap(); + + cx.spawn(|mut cx| async move { + window.update(&mut cx, |_, cx| { + cx.dispatch_action(ToggleFocus.boxed_clone()) + }) + }) + .detach(); + cx.background_executor.run_until_parked(); + window.update(cx, |_, cx| { + search_view.update(cx, |search_view, cx| { + assert!( + search_view.query_editor.focus_handle(cx).is_focused(cx), + "Search view with mismatching query should be focused after the toggle focus event: still no results panel to focus on", + ); + }); + }).unwrap(); + + window + .update(cx, |_, cx| { + search_view.update(cx, |search_view, cx| { + search_view + .query_editor + .update(cx, |query_editor, cx| query_editor.set_text("TWO", cx)); + search_view.search(cx); + }); + }) + .unwrap(); + cx.background_executor.run_until_parked(); + window.update(cx, |_, cx| { + search_view.update(cx, |search_view, cx| { + assert_eq!( + search_view + .results_editor + .update(cx, |editor, cx| editor.display_text(cx)), + "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;", + "Search view results should match the query" + ); + assert!( + search_view.results_editor.focus_handle(cx).is_focused(cx), + "Search view with mismatching query should be focused after search results are available", + ); + }); + }).unwrap(); + cx.spawn(|mut cx| async move { + window + .update(&mut cx, |_, cx| { + cx.dispatch_action(ToggleFocus.boxed_clone()) + }) + .unwrap(); + }) + .detach(); + cx.background_executor.run_until_parked(); + window.update(cx, |_, cx| { + search_view.update(cx, |search_view, cx| { + assert!( + search_view.results_editor.focus_handle(cx).is_focused(cx), + "Search view with matching query should still have its results editor focused after the toggle focus event", + ); + }); + }).unwrap(); + + workspace + .update(cx, |workspace, cx| { + ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch, cx) + }) + .unwrap(); + window.update(cx, |_, cx| { + search_view.update(cx, |search_view, cx| { + assert_eq!(search_view.query_editor.read(cx).text(cx), "two", "Query should be updated to first search result after search view 2nd open in a row"); + assert_eq!( + search_view + .results_editor + .update(cx, |editor, cx| editor.display_text(cx)), + "\n\nconst THREE: usize = one::ONE + two::TWO;\n\n\nconst TWO: usize = one::ONE + one::ONE;", + "Results should be unchanged after search view 2nd open in a row" + ); + assert!( + search_view.query_editor.focus_handle(cx).is_focused(cx), + "Focus should be moved into query editor again after search view 2nd open in a row" + ); + }); + }).unwrap(); + + cx.spawn(|mut cx| async move { + window + .update(&mut cx, |_, cx| { + cx.dispatch_action(ToggleFocus.boxed_clone()) + }) + .unwrap(); + }) + .detach(); + cx.background_executor.run_until_parked(); + window.update(cx, |_, cx| { + search_view.update(cx, |search_view, cx| { + assert!( + search_view.results_editor.focus_handle(cx).is_focused(cx), + "Search view with matching query should switch focus to the results editor after the toggle focus event", + ); + }); + }).unwrap(); + } + + #[gpui::test] + async fn test_new_project_search_focus(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.background_executor.clone()); @@ -2101,7 +2369,7 @@ pub mod tests { .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) }); - ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx) + ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx) }) .unwrap(); @@ -2250,7 +2518,7 @@ pub mod tests { workspace .update(cx, |workspace, cx| { - ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx) + ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx) }) .unwrap(); cx.background_executor.run_until_parked(); @@ -2536,7 +2804,7 @@ pub mod tests { .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) }); - ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx) + ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx) } }) .unwrap(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 69e30a6ccb65c215cd5a59efcb6821549233378d..ba2a2dea9032a646d0657cc12499818d1bb5cb81 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -107,6 +107,7 @@ actions!( NewCenterTerminal, ToggleTerminalFocus, NewSearch, + DeploySearch, Feedback, Restart, Welcome, From 2da314fb79aa098b6053d7309435a69099b06008 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 4 Jan 2024 14:26:08 -0500 Subject: [PATCH 30/67] Fix font resolution for UI text so we render with the fallback font (#3893) This PR updates the font resolution for shaped text to use the new `resolve_font` method on the text system. This makes it so we use the fallback font if the desired font cannot be found rather than rendering nothing. Release Notes: - Fixed an issue where nothing would render when the font set in `ui_font_family` was not found. --- crates/gpui/src/text_system.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 60934b3959722eb76f9a86a16b6669d06109dd74..3106a5a961514157d2daf4d0360c395fae45c2df 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -288,7 +288,7 @@ impl TextSystem { last_font = Some(run.font.clone()); font_runs.push(FontRun { len: run_len_within_line, - font_id: self.platform_text_system.font_id(&run.font)?, + font_id: self.resolve_font(&run.font), }); } From bf8f3e3d68567e5bfde0ee89b6ddd21c483452e1 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 4 Jan 2024 15:07:46 -0500 Subject: [PATCH 31/67] Unbork test relying on old keystroke behavior Previously it would both send the keydown *and then* the action, now it send the action, and then because there was an action, does not send the keydown Co-Authored-By: Conrad Irwin --- crates/gpui/src/interactive.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index 6fc7cfc8e8dfcca3cf91bb35d4da06517a2c75f1..0be917350df812ce07de2e95d1dac52cd59637ad 100644 --- a/crates/gpui/src/interactive.rs +++ b/crates/gpui/src/interactive.rs @@ -307,7 +307,10 @@ mod test { div().id("testview").child( div() .key_context("parent") - .on_key_down(cx.listener(|this, _, _| this.saw_key_down = true)) + .on_key_down(cx.listener(|this, _, cx| { + cx.stop_propagation(); + this.saw_key_down = true + })) .on_action( cx.listener(|this: &mut TestView, _: &TestAction, _| { this.saw_action = true @@ -343,6 +346,7 @@ mod test { .update(cx, |test_view, cx| cx.focus(&test_view.focus_handle)) .unwrap(); + cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap(), false); cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false); window From 931bd687dc339d4c1c2e7fcc0ab5d5d0079db405 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 4 Jan 2024 13:57:59 -0700 Subject: [PATCH 32/67] Uncomment editor tests Co-Authored-By: Julia --- crates/collab/src/tests/editor_tests.rs | 3773 +++++++++++------------ 1 file changed, 1884 insertions(+), 1889 deletions(-) diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 07a4269567d24481231e5c8bfd1ab155c559bb27..c6577014618cfd7fec29c76fb97cf8a10d14a617 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -1,1889 +1,1884 @@ -//todo(partially ported) -// use std::{ -// path::Path, -// sync::{ -// atomic::{self, AtomicBool, AtomicUsize}, -// Arc, -// }, -// }; - -// use call::ActiveCall; -// use editor::{ -// test::editor_test_context::{AssertionContextManager, EditorTestContext}, -// Anchor, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename, -// ToggleCodeActions, Undo, -// }; -// use gpui::{BackgroundExecutor, TestAppContext, VisualContext, VisualTestContext}; -// use indoc::indoc; -// use language::{ -// language_settings::{AllLanguageSettings, InlayHintSettings}, -// tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, -// }; -// use rpc::RECEIVE_TIMEOUT; -// use serde_json::json; -// use settings::SettingsStore; -// use text::Point; -// use workspace::Workspace; - -// use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; - -// #[gpui::test(iterations = 10)] -// async fn test_host_disconnect( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// cx_c: &mut TestAppContext, -// ) { -// let mut server = TestServer::start(executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// let client_b = server.create_client(cx_b, "user_b").await; -// let client_c = server.create_client(cx_c, "user_c").await; -// server -// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) -// .await; - -// cx_b.update(editor::init); - -// client_a -// .fs() -// .insert_tree( -// "/a", -// serde_json::json!({ -// "a.txt": "a-contents", -// "b.txt": "b-contents", -// }), -// ) -// .await; - -// let active_call_a = cx_a.read(ActiveCall::global); -// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; - -// let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees().next().unwrap()); -// let project_id = active_call_a -// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) -// .await -// .unwrap(); - -// let project_b = client_b.build_remote_project(project_id, cx_b).await; -// executor.run_until_parked(); - -// assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); - -// let workspace_b = -// cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx)); -// let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b); - -// let editor_b = workspace_b -// .update(cx_b, |workspace, cx| { -// workspace.open_path((worktree_id, "b.txt"), None, true, cx) -// }) -// .unwrap() -// .await -// .unwrap() -// .downcast::() -// .unwrap(); - -// //TODO: focus -// assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx))); -// editor_b.update(cx_b, |editor, cx| editor.insert("X", cx)); -// //todo(is_edited) -// // assert!(workspace_b.is_edited(cx_b)); - -// // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared. -// server.forbid_connections(); -// server.disconnect_client(client_a.peer_id().unwrap()); -// executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT); - -// project_a.read_with(cx_a, |project, _| project.collaborators().is_empty()); - -// project_a.read_with(cx_a, |project, _| assert!(!project.is_shared())); - -// project_b.read_with(cx_b, |project, _| project.is_read_only()); - -// assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared())); - -// // Ensure client B's edited state is reset and that the whole window is blurred. - -// workspace_b.update(cx_b, |_, cx| { -// assert_eq!(cx.focused_view_id(), None); -// }); -// // assert!(!workspace_b.is_edited(cx_b)); - -// // Ensure client B is not prompted to save edits when closing window after disconnecting. -// let can_close = workspace_b -// .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx)) -// .await -// .unwrap(); -// assert!(can_close); - -// // Allow client A to reconnect to the server. -// server.allow_connections(); -// executor.advance_clock(RECEIVE_TIMEOUT); - -// // Client B calls client A again after they reconnected. -// let active_call_b = cx_b.read(ActiveCall::global); -// active_call_b -// .update(cx_b, |call, cx| { -// call.invite(client_a.user_id().unwrap(), None, cx) -// }) -// .await -// .unwrap(); -// executor.run_until_parked(); -// active_call_a -// .update(cx_a, |call, cx| call.accept_incoming(cx)) -// .await -// .unwrap(); - -// active_call_a -// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) -// .await -// .unwrap(); - -// // Drop client A's connection again. We should still unshare it successfully. -// server.forbid_connections(); -// server.disconnect_client(client_a.peer_id().unwrap()); -// executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT); - -// project_a.read_with(cx_a, |project, _| assert!(!project.is_shared())); -// } - -// #[gpui::test] -// async fn test_newline_above_or_below_does_not_move_guest_cursor( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// ) { -// let mut server = TestServer::start(&executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// let client_b = server.create_client(cx_b, "user_b").await; -// server -// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) -// .await; -// let active_call_a = cx_a.read(ActiveCall::global); - -// client_a -// .fs() -// .insert_tree("/dir", json!({ "a.txt": "Some text\n" })) -// .await; -// let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; -// let project_id = active_call_a -// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) -// .await -// .unwrap(); - -// let project_b = client_b.build_remote_project(project_id, cx_b).await; - -// // Open a buffer as client A -// let buffer_a = project_a -// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) -// .await -// .unwrap(); -// let window_a = cx_a.add_empty_window(); -// let editor_a = -// window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx)); -// let mut editor_cx_a = EditorTestContext { -// cx: cx_a, -// window: window_a.into(), -// editor: editor_a, -// assertion_cx: AssertionContextManager::new(), -// }; - -// // Open a buffer as client B -// let buffer_b = project_b -// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) -// .await -// .unwrap(); -// let window_b = cx_b.add_empty_window(); -// let editor_b = -// window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx)); -// let mut editor_cx_b = EditorTestContext { -// cx: cx_b, -// window: window_b.into(), -// editor: editor_b, -// assertion_cx: AssertionContextManager::new(), -// }; - -// // Test newline above -// editor_cx_a.set_selections_state(indoc! {" -// Some textˇ -// "}); -// editor_cx_b.set_selections_state(indoc! {" -// Some textˇ -// "}); -// editor_cx_a.update_editor(|editor, cx| editor.newline_above(&editor::NewlineAbove, cx)); -// executor.run_until_parked(); -// editor_cx_a.assert_editor_state(indoc! {" -// ˇ -// Some text -// "}); -// editor_cx_b.assert_editor_state(indoc! {" - -// Some textˇ -// "}); - -// // Test newline below -// editor_cx_a.set_selections_state(indoc! {" - -// Some textˇ -// "}); -// editor_cx_b.set_selections_state(indoc! {" - -// Some textˇ -// "}); -// editor_cx_a.update_editor(|editor, cx| editor.newline_below(&editor::NewlineBelow, cx)); -// executor.run_until_parked(); -// editor_cx_a.assert_editor_state(indoc! {" - -// Some text -// ˇ -// "}); -// editor_cx_b.assert_editor_state(indoc! {" - -// Some textˇ - -// "}); -// } - -// #[gpui::test(iterations = 10)] -// async fn test_collaborating_with_completion( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// ) { -// let mut server = TestServer::start(&executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// let client_b = server.create_client(cx_b, "user_b").await; -// server -// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) -// .await; -// let active_call_a = cx_a.read(ActiveCall::global); - -// // Set up a fake language server. -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_language_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// capabilities: lsp::ServerCapabilities { -// completion_provider: Some(lsp::CompletionOptions { -// trigger_characters: Some(vec![".".to_string()]), -// resolve_provider: Some(true), -// ..Default::default() -// }), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; -// client_a.language_registry().add(Arc::new(language)); - -// client_a -// .fs() -// .insert_tree( -// "/a", -// json!({ -// "main.rs": "fn main() { a }", -// "other.rs": "", -// }), -// ) -// .await; -// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; -// let project_id = active_call_a -// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) -// .await -// .unwrap(); -// let project_b = client_b.build_remote_project(project_id, cx_b).await; - -// // Open a file in an editor as the guest. -// let buffer_b = project_b -// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) -// .await -// .unwrap(); -// let window_b = cx_b.add_empty_window(); -// let editor_b = window_b.build_view(cx_b, |cx| { -// Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx) -// }); - -// let fake_language_server = fake_language_servers.next().await.unwrap(); -// cx_a.foreground().run_until_parked(); - -// buffer_b.read_with(cx_b, |buffer, _| { -// assert!(!buffer.completion_triggers().is_empty()) -// }); - -// // Type a completion trigger character as the guest. -// editor_b.update(cx_b, |editor, cx| { -// editor.change_selections(None, cx, |s| s.select_ranges([13..13])); -// editor.handle_input(".", cx); -// cx.focus(&editor_b); -// }); - -// // Receive a completion request as the host's language server. -// // Return some completions from the host's language server. -// cx_a.foreground().start_waiting(); -// fake_language_server -// .handle_request::(|params, _| async move { -// assert_eq!( -// params.text_document_position.text_document.uri, -// lsp::Url::from_file_path("/a/main.rs").unwrap(), -// ); -// assert_eq!( -// params.text_document_position.position, -// lsp::Position::new(0, 14), -// ); - -// Ok(Some(lsp::CompletionResponse::Array(vec![ -// lsp::CompletionItem { -// label: "first_method(…)".into(), -// detail: Some("fn(&mut self, B) -> C".into()), -// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { -// new_text: "first_method($1)".to_string(), -// range: lsp::Range::new( -// lsp::Position::new(0, 14), -// lsp::Position::new(0, 14), -// ), -// })), -// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), -// ..Default::default() -// }, -// lsp::CompletionItem { -// label: "second_method(…)".into(), -// detail: Some("fn(&mut self, C) -> D".into()), -// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { -// new_text: "second_method()".to_string(), -// range: lsp::Range::new( -// lsp::Position::new(0, 14), -// lsp::Position::new(0, 14), -// ), -// })), -// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), -// ..Default::default() -// }, -// ]))) -// }) -// .next() -// .await -// .unwrap(); -// cx_a.foreground().finish_waiting(); - -// // Open the buffer on the host. -// let buffer_a = project_a -// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) -// .await -// .unwrap(); -// cx_a.foreground().run_until_parked(); - -// buffer_a.read_with(cx_a, |buffer, _| { -// assert_eq!(buffer.text(), "fn main() { a. }") -// }); - -// // Confirm a completion on the guest. - -// editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible())); -// editor_b.update(cx_b, |editor, cx| { -// editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx); -// assert_eq!(editor.text(cx), "fn main() { a.first_method() }"); -// }); - -// // Return a resolved completion from the host's language server. -// // The resolved completion has an additional text edit. -// fake_language_server.handle_request::( -// |params, _| async move { -// assert_eq!(params.label, "first_method(…)"); -// Ok(lsp::CompletionItem { -// label: "first_method(…)".into(), -// detail: Some("fn(&mut self, B) -> C".into()), -// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { -// new_text: "first_method($1)".to_string(), -// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)), -// })), -// additional_text_edits: Some(vec![lsp::TextEdit { -// new_text: "use d::SomeTrait;\n".to_string(), -// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)), -// }]), -// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), -// ..Default::default() -// }) -// }, -// ); - -// // The additional edit is applied. -// cx_a.executor().run_until_parked(); - -// buffer_a.read_with(cx_a, |buffer, _| { -// assert_eq!( -// buffer.text(), -// "use d::SomeTrait;\nfn main() { a.first_method() }" -// ); -// }); - -// buffer_b.read_with(cx_b, |buffer, _| { -// assert_eq!( -// buffer.text(), -// "use d::SomeTrait;\nfn main() { a.first_method() }" -// ); -// }); -// } - -// #[gpui::test(iterations = 10)] -// async fn test_collaborating_with_code_actions( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// ) { -// let mut server = TestServer::start(&executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// // -// let client_b = server.create_client(cx_b, "user_b").await; -// server -// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) -// .await; -// let active_call_a = cx_a.read(ActiveCall::global); - -// cx_b.update(editor::init); - -// // Set up a fake language server. -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; -// client_a.language_registry().add(Arc::new(language)); - -// client_a -// .fs() -// .insert_tree( -// "/a", -// json!({ -// "main.rs": "mod other;\nfn main() { let foo = other::foo(); }", -// "other.rs": "pub fn foo() -> usize { 4 }", -// }), -// ) -// .await; -// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; -// let project_id = active_call_a -// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) -// .await -// .unwrap(); - -// // Join the project as client B. -// let project_b = client_b.build_remote_project(project_id, cx_b).await; -// let window_b = -// cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx)); -// let workspace_b = window_b.root(cx_b); -// let editor_b = workspace_b -// .update(cx_b, |workspace, cx| { -// workspace.open_path((worktree_id, "main.rs"), None, true, cx) -// }) -// .await -// .unwrap() -// .downcast::() -// .unwrap(); - -// let mut fake_language_server = fake_language_servers.next().await.unwrap(); -// let mut requests = fake_language_server -// .handle_request::(|params, _| async move { -// assert_eq!( -// params.text_document.uri, -// lsp::Url::from_file_path("/a/main.rs").unwrap(), -// ); -// assert_eq!(params.range.start, lsp::Position::new(0, 0)); -// assert_eq!(params.range.end, lsp::Position::new(0, 0)); -// Ok(None) -// }); -// executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2); -// requests.next().await; - -// // Move cursor to a location that contains code actions. -// editor_b.update(cx_b, |editor, cx| { -// editor.change_selections(None, cx, |s| { -// s.select_ranges([Point::new(1, 31)..Point::new(1, 31)]) -// }); -// cx.focus(&editor_b); -// }); - -// let mut requests = fake_language_server -// .handle_request::(|params, _| async move { -// assert_eq!( -// params.text_document.uri, -// lsp::Url::from_file_path("/a/main.rs").unwrap(), -// ); -// assert_eq!(params.range.start, lsp::Position::new(1, 31)); -// assert_eq!(params.range.end, lsp::Position::new(1, 31)); - -// Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction( -// lsp::CodeAction { -// title: "Inline into all callers".to_string(), -// edit: Some(lsp::WorkspaceEdit { -// changes: Some( -// [ -// ( -// lsp::Url::from_file_path("/a/main.rs").unwrap(), -// vec![lsp::TextEdit::new( -// lsp::Range::new( -// lsp::Position::new(1, 22), -// lsp::Position::new(1, 34), -// ), -// "4".to_string(), -// )], -// ), -// ( -// lsp::Url::from_file_path("/a/other.rs").unwrap(), -// vec![lsp::TextEdit::new( -// lsp::Range::new( -// lsp::Position::new(0, 0), -// lsp::Position::new(0, 27), -// ), -// "".to_string(), -// )], -// ), -// ] -// .into_iter() -// .collect(), -// ), -// ..Default::default() -// }), -// data: Some(json!({ -// "codeActionParams": { -// "range": { -// "start": {"line": 1, "column": 31}, -// "end": {"line": 1, "column": 31}, -// } -// } -// })), -// ..Default::default() -// }, -// )])) -// }); -// executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2); -// requests.next().await; - -// // Toggle code actions and wait for them to display. -// editor_b.update(cx_b, |editor, cx| { -// editor.toggle_code_actions( -// &ToggleCodeActions { -// deployed_from_indicator: false, -// }, -// cx, -// ); -// }); -// cx_a.foreground().run_until_parked(); - -// editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible())); - -// fake_language_server.remove_request_handler::(); - -// // Confirming the code action will trigger a resolve request. -// let confirm_action = workspace_b -// .update(cx_b, |workspace, cx| { -// Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx) -// }) -// .unwrap(); -// fake_language_server.handle_request::( -// |_, _| async move { -// Ok(lsp::CodeAction { -// title: "Inline into all callers".to_string(), -// edit: Some(lsp::WorkspaceEdit { -// changes: Some( -// [ -// ( -// lsp::Url::from_file_path("/a/main.rs").unwrap(), -// vec![lsp::TextEdit::new( -// lsp::Range::new( -// lsp::Position::new(1, 22), -// lsp::Position::new(1, 34), -// ), -// "4".to_string(), -// )], -// ), -// ( -// lsp::Url::from_file_path("/a/other.rs").unwrap(), -// vec![lsp::TextEdit::new( -// lsp::Range::new( -// lsp::Position::new(0, 0), -// lsp::Position::new(0, 27), -// ), -// "".to_string(), -// )], -// ), -// ] -// .into_iter() -// .collect(), -// ), -// ..Default::default() -// }), -// ..Default::default() -// }) -// }, -// ); - -// // After the action is confirmed, an editor containing both modified files is opened. -// confirm_action.await.unwrap(); - -// let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| { -// workspace -// .active_item(cx) -// .unwrap() -// .downcast::() -// .unwrap() -// }); -// code_action_editor.update(cx_b, |editor, cx| { -// assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n"); -// editor.undo(&Undo, cx); -// assert_eq!( -// editor.text(cx), -// "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }" -// ); -// editor.redo(&Redo, cx); -// assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n"); -// }); -// } - -// #[gpui::test(iterations = 10)] -// async fn test_collaborating_with_renames( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// ) { -// let mut server = TestServer::start(&executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// let client_b = server.create_client(cx_b, "user_b").await; -// server -// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) -// .await; -// let active_call_a = cx_a.read(ActiveCall::global); - -// cx_b.update(editor::init); - -// // Set up a fake language server. -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_language_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// capabilities: lsp::ServerCapabilities { -// rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { -// prepare_provider: Some(true), -// work_done_progress_options: Default::default(), -// })), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; -// client_a.language_registry().add(Arc::new(language)); - -// client_a -// .fs() -// .insert_tree( -// "/dir", -// json!({ -// "one.rs": "const ONE: usize = 1;", -// "two.rs": "const TWO: usize = one::ONE + one::ONE;" -// }), -// ) -// .await; -// let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; -// let project_id = active_call_a -// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) -// .await -// .unwrap(); -// let project_b = client_b.build_remote_project(project_id, cx_b).await; - -// let window_b = -// cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx)); -// let workspace_b = window_b.root(cx_b); -// let editor_b = workspace_b -// .update(cx_b, |workspace, cx| { -// workspace.open_path((worktree_id, "one.rs"), None, true, cx) -// }) -// .await -// .unwrap() -// .downcast::() -// .unwrap(); -// let fake_language_server = fake_language_servers.next().await.unwrap(); - -// // Move cursor to a location that can be renamed. -// let prepare_rename = editor_b.update(cx_b, |editor, cx| { -// editor.change_selections(None, cx, |s| s.select_ranges([7..7])); -// editor.rename(&Rename, cx).unwrap() -// }); - -// fake_language_server -// .handle_request::(|params, _| async move { -// assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs"); -// assert_eq!(params.position, lsp::Position::new(0, 7)); -// Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( -// lsp::Position::new(0, 6), -// lsp::Position::new(0, 9), -// )))) -// }) -// .next() -// .await -// .unwrap(); -// prepare_rename.await.unwrap(); -// editor_b.update(cx_b, |editor, cx| { -// use editor::ToOffset; -// let rename = editor.pending_rename().unwrap(); -// let buffer = editor.buffer().read(cx).snapshot(cx); -// assert_eq!( -// rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer), -// 6..9 -// ); -// rename.editor.update(cx, |rename_editor, cx| { -// rename_editor.buffer().update(cx, |rename_buffer, cx| { -// rename_buffer.edit([(0..3, "THREE")], None, cx); -// }); -// }); -// }); - -// let confirm_rename = workspace_b.update(cx_b, |workspace, cx| { -// Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap() -// }); -// fake_language_server -// .handle_request::(|params, _| async move { -// assert_eq!( -// params.text_document_position.text_document.uri.as_str(), -// "file:///dir/one.rs" -// ); -// assert_eq!( -// params.text_document_position.position, -// lsp::Position::new(0, 6) -// ); -// assert_eq!(params.new_name, "THREE"); -// Ok(Some(lsp::WorkspaceEdit { -// changes: Some( -// [ -// ( -// lsp::Url::from_file_path("/dir/one.rs").unwrap(), -// vec![lsp::TextEdit::new( -// lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)), -// "THREE".to_string(), -// )], -// ), -// ( -// lsp::Url::from_file_path("/dir/two.rs").unwrap(), -// vec![ -// lsp::TextEdit::new( -// lsp::Range::new( -// lsp::Position::new(0, 24), -// lsp::Position::new(0, 27), -// ), -// "THREE".to_string(), -// ), -// lsp::TextEdit::new( -// lsp::Range::new( -// lsp::Position::new(0, 35), -// lsp::Position::new(0, 38), -// ), -// "THREE".to_string(), -// ), -// ], -// ), -// ] -// .into_iter() -// .collect(), -// ), -// ..Default::default() -// })) -// }) -// .next() -// .await -// .unwrap(); -// confirm_rename.await.unwrap(); - -// let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| { -// workspace -// .active_item(cx) -// .unwrap() -// .downcast::() -// .unwrap() -// }); -// rename_editor.update(cx_b, |editor, cx| { -// assert_eq!( -// editor.text(cx), -// "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;" -// ); -// editor.undo(&Undo, cx); -// assert_eq!( -// editor.text(cx), -// "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;" -// ); -// editor.redo(&Redo, cx); -// assert_eq!( -// editor.text(cx), -// "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;" -// ); -// }); - -// // Ensure temporary rename edits cannot be undone/redone. -// editor_b.update(cx_b, |editor, cx| { -// editor.undo(&Undo, cx); -// assert_eq!(editor.text(cx), "const ONE: usize = 1;"); -// editor.undo(&Undo, cx); -// assert_eq!(editor.text(cx), "const ONE: usize = 1;"); -// editor.redo(&Redo, cx); -// assert_eq!(editor.text(cx), "const THREE: usize = 1;"); -// }) -// } - -// #[gpui::test(iterations = 10)] -// async fn test_language_server_statuses( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// ) { -// let mut server = TestServer::start(&executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// let client_b = server.create_client(cx_b, "user_b").await; -// server -// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) -// .await; -// let active_call_a = cx_a.read(ActiveCall::global); - -// cx_b.update(editor::init); - -// // Set up a fake language server. -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_language_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// name: "the-language-server", -// ..Default::default() -// })) -// .await; -// client_a.language_registry().add(Arc::new(language)); - -// client_a -// .fs() -// .insert_tree( -// "/dir", -// json!({ -// "main.rs": "const ONE: usize = 1;", -// }), -// ) -// .await; -// let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; - -// let _buffer_a = project_a -// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) -// .await -// .unwrap(); - -// let fake_language_server = fake_language_servers.next().await.unwrap(); -// fake_language_server.start_progress("the-token").await; -// fake_language_server.notify::(lsp::ProgressParams { -// token: lsp::NumberOrString::String("the-token".to_string()), -// value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report( -// lsp::WorkDoneProgressReport { -// message: Some("the-message".to_string()), -// ..Default::default() -// }, -// )), -// }); -// executor.run_until_parked(); - -// project_a.read_with(cx_a, |project, _| { -// let status = project.language_server_statuses().next().unwrap(); -// assert_eq!(status.name, "the-language-server"); -// assert_eq!(status.pending_work.len(), 1); -// assert_eq!( -// status.pending_work["the-token"].message.as_ref().unwrap(), -// "the-message" -// ); -// }); - -// let project_id = active_call_a -// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) -// .await -// .unwrap(); -// executor.run_until_parked(); -// let project_b = client_b.build_remote_project(project_id, cx_b).await; - -// project_b.read_with(cx_b, |project, _| { -// let status = project.language_server_statuses().next().unwrap(); -// assert_eq!(status.name, "the-language-server"); -// }); - -// fake_language_server.notify::(lsp::ProgressParams { -// token: lsp::NumberOrString::String("the-token".to_string()), -// value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report( -// lsp::WorkDoneProgressReport { -// message: Some("the-message-2".to_string()), -// ..Default::default() -// }, -// )), -// }); -// executor.run_until_parked(); - -// project_a.read_with(cx_a, |project, _| { -// let status = project.language_server_statuses().next().unwrap(); -// assert_eq!(status.name, "the-language-server"); -// assert_eq!(status.pending_work.len(), 1); -// assert_eq!( -// status.pending_work["the-token"].message.as_ref().unwrap(), -// "the-message-2" -// ); -// }); - -// project_b.read_with(cx_b, |project, _| { -// let status = project.language_server_statuses().next().unwrap(); -// assert_eq!(status.name, "the-language-server"); -// assert_eq!(status.pending_work.len(), 1); -// assert_eq!( -// status.pending_work["the-token"].message.as_ref().unwrap(), -// "the-message-2" -// ); -// }); -// } - -// #[gpui::test(iterations = 10)] -// async fn test_share_project( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// cx_c: &mut TestAppContext, -// ) { -// let window_b = cx_b.add_empty_window(); -// let mut server = TestServer::start(executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// let client_b = server.create_client(cx_b, "user_b").await; -// let client_c = server.create_client(cx_c, "user_c").await; -// server -// .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) -// .await; -// let active_call_a = cx_a.read(ActiveCall::global); -// let active_call_b = cx_b.read(ActiveCall::global); -// let active_call_c = cx_c.read(ActiveCall::global); - -// client_a -// .fs() -// .insert_tree( -// "/a", -// json!({ -// ".gitignore": "ignored-dir", -// "a.txt": "a-contents", -// "b.txt": "b-contents", -// "ignored-dir": { -// "c.txt": "", -// "d.txt": "", -// } -// }), -// ) -// .await; - -// // Invite client B to collaborate on a project -// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; -// active_call_a -// .update(cx_a, |call, cx| { -// call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx) -// }) -// .await -// .unwrap(); - -// // Join that project as client B - -// let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming()); -// executor.run_until_parked(); -// let call = incoming_call_b.borrow().clone().unwrap(); -// assert_eq!(call.calling_user.github_login, "user_a"); -// let initial_project = call.initial_project.unwrap(); -// active_call_b -// .update(cx_b, |call, cx| call.accept_incoming(cx)) -// .await -// .unwrap(); -// let client_b_peer_id = client_b.peer_id().unwrap(); -// let project_b = client_b -// .build_remote_project(initial_project.id, cx_b) -// .await; - -// let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id()); - -// executor.run_until_parked(); - -// project_a.read_with(cx_a, |project, _| { -// let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap(); -// assert_eq!(client_b_collaborator.replica_id, replica_id_b); -// }); - -// project_b.read_with(cx_b, |project, cx| { -// let worktree = project.worktrees().next().unwrap().read(cx); -// assert_eq!( -// worktree.paths().map(AsRef::as_ref).collect::>(), -// [ -// Path::new(".gitignore"), -// Path::new("a.txt"), -// Path::new("b.txt"), -// Path::new("ignored-dir"), -// ] -// ); -// }); - -// project_b -// .update(cx_b, |project, cx| { -// let worktree = project.worktrees().next().unwrap(); -// let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap(); -// project.expand_entry(worktree_id, entry.id, cx).unwrap() -// }) -// .await -// .unwrap(); - -// project_b.read_with(cx_b, |project, cx| { -// let worktree = project.worktrees().next().unwrap().read(cx); -// assert_eq!( -// worktree.paths().map(AsRef::as_ref).collect::>(), -// [ -// Path::new(".gitignore"), -// Path::new("a.txt"), -// Path::new("b.txt"), -// Path::new("ignored-dir"), -// Path::new("ignored-dir/c.txt"), -// Path::new("ignored-dir/d.txt"), -// ] -// ); -// }); - -// // Open the same file as client B and client A. -// let buffer_b = project_b -// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx)) -// .await -// .unwrap(); - -// buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents")); - -// project_a.read_with(cx_a, |project, cx| { -// assert!(project.has_open_buffer((worktree_id, "b.txt"), cx)) -// }); -// let buffer_a = project_a -// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx)) -// .await -// .unwrap(); - -// let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx)); - -// // Client A sees client B's selection -// executor.run_until_parked(); - -// buffer_a.read_with(cx_a, |buffer, _| { -// buffer -// .snapshot() -// .remote_selections_in_range(Anchor::MIN..Anchor::MAX) -// .count() -// == 1 -// }); - -// // Edit the buffer as client B and see that edit as client A. -// editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx)); -// executor.run_until_parked(); - -// buffer_a.read_with(cx_a, |buffer, _| { -// assert_eq!(buffer.text(), "ok, b-contents") -// }); - -// // Client B can invite client C on a project shared by client A. -// active_call_b -// .update(cx_b, |call, cx| { -// call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx) -// }) -// .await -// .unwrap(); - -// let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming()); -// executor.run_until_parked(); -// let call = incoming_call_c.borrow().clone().unwrap(); -// assert_eq!(call.calling_user.github_login, "user_b"); -// let initial_project = call.initial_project.unwrap(); -// active_call_c -// .update(cx_c, |call, cx| call.accept_incoming(cx)) -// .await -// .unwrap(); -// let _project_c = client_c -// .build_remote_project(initial_project.id, cx_c) -// .await; - -// // Client B closes the editor, and client A sees client B's selections removed. -// cx_b.update(move |_| drop(editor_b)); -// executor.run_until_parked(); - -// buffer_a.read_with(cx_a, |buffer, _| { -// buffer -// .snapshot() -// .remote_selections_in_range(Anchor::MIN..Anchor::MAX) -// .count() -// == 0 -// }); -// } - -// #[gpui::test(iterations = 10)] -// async fn test_on_input_format_from_host_to_guest( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// ) { -// let mut server = TestServer::start(&executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// let client_b = server.create_client(cx_b, "user_b").await; -// server -// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) -// .await; -// let active_call_a = cx_a.read(ActiveCall::global); - -// // Set up a fake language server. -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_language_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// capabilities: lsp::ServerCapabilities { -// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions { -// first_trigger_character: ":".to_string(), -// more_trigger_character: Some(vec![">".to_string()]), -// }), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; -// client_a.language_registry().add(Arc::new(language)); - -// client_a -// .fs() -// .insert_tree( -// "/a", -// json!({ -// "main.rs": "fn main() { a }", -// "other.rs": "// Test file", -// }), -// ) -// .await; -// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; -// let project_id = active_call_a -// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) -// .await -// .unwrap(); -// let project_b = client_b.build_remote_project(project_id, cx_b).await; - -// // Open a file in an editor as the host. -// let buffer_a = project_a -// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) -// .await -// .unwrap(); -// let window_a = cx_a.add_empty_window(); -// let editor_a = window_a -// .update(cx_a, |_, cx| { -// cx.build_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)) -// }) -// .unwrap(); - -// let fake_language_server = fake_language_servers.next().await.unwrap(); -// executor.run_until_parked(); - -// // Receive an OnTypeFormatting request as the host's language server. -// // Return some formattings from the host's language server. -// fake_language_server.handle_request::( -// |params, _| async move { -// assert_eq!( -// params.text_document_position.text_document.uri, -// lsp::Url::from_file_path("/a/main.rs").unwrap(), -// ); -// assert_eq!( -// params.text_document_position.position, -// lsp::Position::new(0, 14), -// ); - -// Ok(Some(vec![lsp::TextEdit { -// new_text: "~<".to_string(), -// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)), -// }])) -// }, -// ); - -// // Open the buffer on the guest and see that the formattings worked -// let buffer_b = project_b -// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) -// .await -// .unwrap(); - -// // Type a on type formatting trigger character as the guest. -// editor_a.update(cx_a, |editor, cx| { -// cx.focus(&editor_a); -// editor.change_selections(None, cx, |s| s.select_ranges([13..13])); -// editor.handle_input(">", cx); -// }); - -// executor.run_until_parked(); - -// buffer_b.read_with(cx_b, |buffer, _| { -// assert_eq!(buffer.text(), "fn main() { a>~< }") -// }); - -// // Undo should remove LSP edits first -// editor_a.update(cx_a, |editor, cx| { -// assert_eq!(editor.text(cx), "fn main() { a>~< }"); -// editor.undo(&Undo, cx); -// assert_eq!(editor.text(cx), "fn main() { a> }"); -// }); -// executor.run_until_parked(); - -// buffer_b.read_with(cx_b, |buffer, _| { -// assert_eq!(buffer.text(), "fn main() { a> }") -// }); - -// editor_a.update(cx_a, |editor, cx| { -// assert_eq!(editor.text(cx), "fn main() { a> }"); -// editor.undo(&Undo, cx); -// assert_eq!(editor.text(cx), "fn main() { a }"); -// }); -// executor.run_until_parked(); - -// buffer_b.read_with(cx_b, |buffer, _| { -// assert_eq!(buffer.text(), "fn main() { a }") -// }); -// } - -// #[gpui::test(iterations = 10)] -// async fn test_on_input_format_from_guest_to_host( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// ) { -// let mut server = TestServer::start(&executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// let client_b = server.create_client(cx_b, "user_b").await; -// server -// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) -// .await; -// let active_call_a = cx_a.read(ActiveCall::global); - -// // Set up a fake language server. -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_language_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// capabilities: lsp::ServerCapabilities { -// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions { -// first_trigger_character: ":".to_string(), -// more_trigger_character: Some(vec![">".to_string()]), -// }), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; -// client_a.language_registry().add(Arc::new(language)); - -// client_a -// .fs() -// .insert_tree( -// "/a", -// json!({ -// "main.rs": "fn main() { a }", -// "other.rs": "// Test file", -// }), -// ) -// .await; -// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; -// let project_id = active_call_a -// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) -// .await -// .unwrap(); -// let project_b = client_b.build_remote_project(project_id, cx_b).await; - -// // Open a file in an editor as the guest. -// let buffer_b = project_b -// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) -// .await -// .unwrap(); -// let window_b = cx_b.add_empty_window(); -// let editor_b = window_b.build_view(cx_b, |cx| { -// Editor::for_buffer(buffer_b, Some(project_b.clone()), cx) -// }); - -// let fake_language_server = fake_language_servers.next().await.unwrap(); -// executor.run_until_parked(); -// // Type a on type formatting trigger character as the guest. -// editor_b.update(cx_b, |editor, cx| { -// editor.change_selections(None, cx, |s| s.select_ranges([13..13])); -// editor.handle_input(":", cx); -// cx.focus(&editor_b); -// }); - -// // Receive an OnTypeFormatting request as the host's language server. -// // Return some formattings from the host's language server. -// cx_a.foreground().start_waiting(); -// fake_language_server -// .handle_request::(|params, _| async move { -// assert_eq!( -// params.text_document_position.text_document.uri, -// lsp::Url::from_file_path("/a/main.rs").unwrap(), -// ); -// assert_eq!( -// params.text_document_position.position, -// lsp::Position::new(0, 14), -// ); - -// Ok(Some(vec![lsp::TextEdit { -// new_text: "~:".to_string(), -// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)), -// }])) -// }) -// .next() -// .await -// .unwrap(); -// cx_a.foreground().finish_waiting(); - -// // Open the buffer on the host and see that the formattings worked -// let buffer_a = project_a -// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) -// .await -// .unwrap(); -// executor.run_until_parked(); - -// buffer_a.read_with(cx_a, |buffer, _| { -// assert_eq!(buffer.text(), "fn main() { a:~: }") -// }); - -// // Undo should remove LSP edits first -// editor_b.update(cx_b, |editor, cx| { -// assert_eq!(editor.text(cx), "fn main() { a:~: }"); -// editor.undo(&Undo, cx); -// assert_eq!(editor.text(cx), "fn main() { a: }"); -// }); -// executor.run_until_parked(); - -// buffer_a.read_with(cx_a, |buffer, _| { -// assert_eq!(buffer.text(), "fn main() { a: }") -// }); - -// editor_b.update(cx_b, |editor, cx| { -// assert_eq!(editor.text(cx), "fn main() { a: }"); -// editor.undo(&Undo, cx); -// assert_eq!(editor.text(cx), "fn main() { a }"); -// }); -// executor.run_until_parked(); - -// buffer_a.read_with(cx_a, |buffer, _| { -// assert_eq!(buffer.text(), "fn main() { a }") -// }); -// } - -// #[gpui::test(iterations = 10)] -// async fn test_mutual_editor_inlay_hint_cache_update( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// ) { -// let mut server = TestServer::start(&executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// let client_b = server.create_client(cx_b, "user_b").await; -// server -// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) -// .await; -// let active_call_a = cx_a.read(ActiveCall::global); -// let active_call_b = cx_b.read(ActiveCall::global); - -// cx_a.update(editor::init); -// cx_b.update(editor::init); - -// cx_a.update(|cx| { -// cx.update_global(|store: &mut SettingsStore, cx| { -// store.update_user_settings::(cx, |settings| { -// settings.defaults.inlay_hints = Some(InlayHintSettings { -// enabled: true, -// show_type_hints: true, -// show_parameter_hints: false, -// show_other_hints: true, -// }) -// }); -// }); -// }); -// cx_b.update(|cx| { -// cx.update_global(|store: &mut SettingsStore, cx| { -// store.update_user_settings::(cx, |settings| { -// settings.defaults.inlay_hints = Some(InlayHintSettings { -// enabled: true, -// show_type_hints: true, -// show_parameter_hints: false, -// show_other_hints: true, -// }) -// }); -// }); -// }); - -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_language_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// capabilities: lsp::ServerCapabilities { -// inlay_hint_provider: Some(lsp::OneOf::Left(true)), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; -// let language = Arc::new(language); -// client_a.language_registry().add(Arc::clone(&language)); -// client_b.language_registry().add(language); - -// // Client A opens a project. -// client_a -// .fs() -// .insert_tree( -// "/a", -// json!({ -// "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out", -// "other.rs": "// Test file", -// }), -// ) -// .await; -// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; -// active_call_a -// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) -// .await -// .unwrap(); -// let project_id = active_call_a -// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) -// .await -// .unwrap(); - -// // Client B joins the project -// let project_b = client_b.build_remote_project(project_id, cx_b).await; -// active_call_b -// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) -// .await -// .unwrap(); - -// let workspace_a = client_a.build_workspace(&project_a, cx_a).root_view(cx_a); -// cx_a.foreground().start_waiting(); - -// // The host opens a rust file. -// let _buffer_a = project_a -// .update(cx_a, |project, cx| { -// project.open_local_buffer("/a/main.rs", cx) -// }) -// .await -// .unwrap(); -// let fake_language_server = fake_language_servers.next().await.unwrap(); -// let editor_a = workspace_a -// .update(cx_a, |workspace, cx| { -// workspace.open_path((worktree_id, "main.rs"), None, true, cx) -// }) -// .await -// .unwrap() -// .downcast::() -// .unwrap(); - -// // Set up the language server to return an additional inlay hint on each request. -// let edits_made = Arc::new(AtomicUsize::new(0)); -// let closure_edits_made = Arc::clone(&edits_made); -// fake_language_server -// .handle_request::(move |params, _| { -// let task_edits_made = Arc::clone(&closure_edits_made); -// async move { -// assert_eq!( -// params.text_document.uri, -// lsp::Url::from_file_path("/a/main.rs").unwrap(), -// ); -// let edits_made = task_edits_made.load(atomic::Ordering::Acquire); -// Ok(Some(vec![lsp::InlayHint { -// position: lsp::Position::new(0, edits_made as u32), -// label: lsp::InlayHintLabel::String(edits_made.to_string()), -// kind: None, -// text_edits: None, -// tooltip: None, -// padding_left: None, -// padding_right: None, -// data: None, -// }])) -// } -// }) -// .next() -// .await -// .unwrap(); - -// executor.run_until_parked(); - -// let initial_edit = edits_made.load(atomic::Ordering::Acquire); -// editor_a.update(cx_a, |editor, _| { -// assert_eq!( -// vec![initial_edit.to_string()], -// extract_hint_labels(editor), -// "Host should get its first hints when opens an editor" -// ); -// let inlay_cache = editor.inlay_hint_cache(); -// assert_eq!( -// inlay_cache.version(), -// 1, -// "Host editor update the cache version after every cache/view change", -// ); -// }); -// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b); -// let editor_b = workspace_b -// .update(cx_b, |workspace, cx| { -// workspace.open_path((worktree_id, "main.rs"), None, true, cx) -// }) -// .await -// .unwrap() -// .downcast::() -// .unwrap(); - -// executor.run_until_parked(); -// editor_b.update(cx_b, |editor, _| { -// assert_eq!( -// vec![initial_edit.to_string()], -// extract_hint_labels(editor), -// "Client should get its first hints when opens an editor" -// ); -// let inlay_cache = editor.inlay_hint_cache(); -// assert_eq!( -// inlay_cache.version(), -// 1, -// "Guest editor update the cache version after every cache/view change" -// ); -// }); - -// let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1; -// editor_b.update(cx_b, |editor, cx| { -// editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone())); -// editor.handle_input(":", cx); -// cx.focus(&editor_b); -// }); - -// executor.run_until_parked(); -// editor_a.update(cx_a, |editor, _| { -// assert_eq!( -// vec![after_client_edit.to_string()], -// extract_hint_labels(editor), -// ); -// let inlay_cache = editor.inlay_hint_cache(); -// assert_eq!(inlay_cache.version(), 2); -// }); -// editor_b.update(cx_b, |editor, _| { -// assert_eq!( -// vec![after_client_edit.to_string()], -// extract_hint_labels(editor), -// ); -// let inlay_cache = editor.inlay_hint_cache(); -// assert_eq!(inlay_cache.version(), 2); -// }); - -// let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1; -// editor_a.update(cx_a, |editor, cx| { -// editor.change_selections(None, cx, |s| s.select_ranges([13..13])); -// editor.handle_input("a change to increment both buffers' versions", cx); -// cx.focus(&editor_a); -// }); - -// executor.run_until_parked(); -// editor_a.update(cx_a, |editor, _| { -// assert_eq!( -// vec![after_host_edit.to_string()], -// extract_hint_labels(editor), -// ); -// let inlay_cache = editor.inlay_hint_cache(); -// assert_eq!(inlay_cache.version(), 3); -// }); -// editor_b.update(cx_b, |editor, _| { -// assert_eq!( -// vec![after_host_edit.to_string()], -// extract_hint_labels(editor), -// ); -// let inlay_cache = editor.inlay_hint_cache(); -// assert_eq!(inlay_cache.version(), 3); -// }); - -// let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1; -// fake_language_server -// .request::(()) -// .await -// .expect("inlay refresh request failed"); - -// executor.run_until_parked(); -// editor_a.update(cx_a, |editor, _| { -// assert_eq!( -// vec![after_special_edit_for_refresh.to_string()], -// extract_hint_labels(editor), -// "Host should react to /refresh LSP request" -// ); -// let inlay_cache = editor.inlay_hint_cache(); -// assert_eq!( -// inlay_cache.version(), -// 4, -// "Host should accepted all edits and bump its cache version every time" -// ); -// }); -// editor_b.update(cx_b, |editor, _| { -// assert_eq!( -// vec![after_special_edit_for_refresh.to_string()], -// extract_hint_labels(editor), -// "Guest should get a /refresh LSP request propagated by host" -// ); -// let inlay_cache = editor.inlay_hint_cache(); -// assert_eq!( -// inlay_cache.version(), -// 4, -// "Guest should accepted all edits and bump its cache version every time" -// ); -// }); -// } - -// #[gpui::test(iterations = 10)] -// async fn test_inlay_hint_refresh_is_forwarded( -// executor: BackgroundExecutor, -// cx_a: &mut TestAppContext, -// cx_b: &mut TestAppContext, -// ) { -// let mut server = TestServer::start(&executor).await; -// let client_a = server.create_client(cx_a, "user_a").await; -// let client_b = server.create_client(cx_b, "user_b").await; -// server -// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) -// .await; -// let active_call_a = cx_a.read(ActiveCall::global); -// let active_call_b = cx_b.read(ActiveCall::global); - -// cx_a.update(editor::init); -// cx_b.update(editor::init); - -// cx_a.update(|cx| { -// cx.update_global(|store: &mut SettingsStore, cx| { -// store.update_user_settings::(cx, |settings| { -// settings.defaults.inlay_hints = Some(InlayHintSettings { -// enabled: false, -// show_type_hints: false, -// show_parameter_hints: false, -// show_other_hints: false, -// }) -// }); -// }); -// }); -// cx_b.update(|cx| { -// cx.update_global(|store: &mut SettingsStore, cx| { -// store.update_user_settings::(cx, |settings| { -// settings.defaults.inlay_hints = Some(InlayHintSettings { -// enabled: true, -// show_type_hints: true, -// show_parameter_hints: true, -// show_other_hints: true, -// }) -// }); -// }); -// }); - -// let mut language = Language::new( -// LanguageConfig { -// name: "Rust".into(), -// path_suffixes: vec!["rs".to_string()], -// ..Default::default() -// }, -// Some(tree_sitter_rust::language()), -// ); -// let mut fake_language_servers = language -// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { -// capabilities: lsp::ServerCapabilities { -// inlay_hint_provider: Some(lsp::OneOf::Left(true)), -// ..Default::default() -// }, -// ..Default::default() -// })) -// .await; -// let language = Arc::new(language); -// client_a.language_registry().add(Arc::clone(&language)); -// client_b.language_registry().add(language); - -// client_a -// .fs() -// .insert_tree( -// "/a", -// json!({ -// "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out", -// "other.rs": "// Test file", -// }), -// ) -// .await; -// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; -// active_call_a -// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) -// .await -// .unwrap(); -// let project_id = active_call_a -// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) -// .await -// .unwrap(); - -// let project_b = client_b.build_remote_project(project_id, cx_b).await; -// active_call_b -// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) -// .await -// .unwrap(); - -// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a); -// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b); -// cx_a.foreground().start_waiting(); -// cx_b.foreground().start_waiting(); - -// let editor_a = workspace_a -// .update(cx_a, |workspace, cx| { -// workspace.open_path((worktree_id, "main.rs"), None, true, cx) -// }) -// .await -// .unwrap() -// .downcast::() -// .unwrap(); - -// let editor_b = workspace_b -// .update(cx_b, |workspace, cx| { -// workspace.open_path((worktree_id, "main.rs"), None, true, cx) -// }) -// .await -// .unwrap() -// .downcast::() -// .unwrap(); - -// let other_hints = Arc::new(AtomicBool::new(false)); -// let fake_language_server = fake_language_servers.next().await.unwrap(); -// let closure_other_hints = Arc::clone(&other_hints); -// fake_language_server -// .handle_request::(move |params, _| { -// let task_other_hints = Arc::clone(&closure_other_hints); -// async move { -// assert_eq!( -// params.text_document.uri, -// lsp::Url::from_file_path("/a/main.rs").unwrap(), -// ); -// let other_hints = task_other_hints.load(atomic::Ordering::Acquire); -// let character = if other_hints { 0 } else { 2 }; -// let label = if other_hints { -// "other hint" -// } else { -// "initial hint" -// }; -// Ok(Some(vec![lsp::InlayHint { -// position: lsp::Position::new(0, character), -// label: lsp::InlayHintLabel::String(label.to_string()), -// kind: None, -// text_edits: None, -// tooltip: None, -// padding_left: None, -// padding_right: None, -// data: None, -// }])) -// } -// }) -// .next() -// .await -// .unwrap(); -// cx_a.foreground().finish_waiting(); -// cx_b.foreground().finish_waiting(); - -// executor.run_until_parked(); -// editor_a.update(cx_a, |editor, _| { -// assert!( -// extract_hint_labels(editor).is_empty(), -// "Host should get no hints due to them turned off" -// ); -// let inlay_cache = editor.inlay_hint_cache(); -// assert_eq!( -// inlay_cache.version(), -// 0, -// "Turned off hints should not generate version updates" -// ); -// }); - -// executor.run_until_parked(); -// editor_b.update(cx_b, |editor, _| { -// assert_eq!( -// vec!["initial hint".to_string()], -// extract_hint_labels(editor), -// "Client should get its first hints when opens an editor" -// ); -// let inlay_cache = editor.inlay_hint_cache(); -// assert_eq!( -// inlay_cache.version(), -// 1, -// "Should update cache verison after first hints" -// ); -// }); - -// other_hints.fetch_or(true, atomic::Ordering::Release); -// fake_language_server -// .request::(()) -// .await -// .expect("inlay refresh request failed"); -// executor.run_until_parked(); -// editor_a.update(cx_a, |editor, _| { -// assert!( -// extract_hint_labels(editor).is_empty(), -// "Host should get nop hints due to them turned off, even after the /refresh" -// ); -// let inlay_cache = editor.inlay_hint_cache(); -// assert_eq!( -// inlay_cache.version(), -// 0, -// "Turned off hints should not generate version updates, again" -// ); -// }); - -// executor.run_until_parked(); -// editor_b.update(cx_b, |editor, _| { -// assert_eq!( -// vec!["other hint".to_string()], -// extract_hint_labels(editor), -// "Guest should get a /refresh LSP request propagated by host despite host hints are off" -// ); -// let inlay_cache = editor.inlay_hint_cache(); -// assert_eq!( -// inlay_cache.version(), -// 2, -// "Guest should accepted all edits and bump its cache version every time" -// ); -// }); -// } - -// fn extract_hint_labels(editor: &Editor) -> Vec { -// let mut labels = Vec::new(); -// for hint in editor.inlay_hint_cache().hints() { -// match hint.label { -// project::InlayHintLabel::String(s) => labels.push(s), -// _ => unreachable!(), -// } -// } -// labels -// } +use std::{ + path::Path, + sync::{ + atomic::{self, AtomicBool, AtomicUsize}, + Arc, + }, +}; + +use call::ActiveCall; +use editor::{ + test::editor_test_context::{AssertionContextManager, EditorTestContext}, + ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename, ToggleCodeActions, + Undo, +}; +use futures::StreamExt; +use gpui::{TestAppContext, VisualContext, VisualTestContext}; +use indoc::indoc; +use language::{ + language_settings::{AllLanguageSettings, InlayHintSettings}, + tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig, +}; +use rpc::RECEIVE_TIMEOUT; +use serde_json::json; +use settings::SettingsStore; +use text::Point; +use workspace::Workspace; + +use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; + +#[gpui::test(iterations = 10)] +async fn test_host_disconnect( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, + cx_c: &mut TestAppContext, +) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let client_c = server.create_client(cx_c, "user_c").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) + .await; + + cx_b.update(editor::init); + + client_a + .fs() + .insert_tree( + "/a", + serde_json::json!({ + "a.txt": "a-contents", + "b.txt": "b-contents", + }), + ) + .await; + + let active_call_a = cx_a.read(ActiveCall::global); + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + + let worktree_a = project_a.read_with(cx_a, |project, _| project.worktrees().next().unwrap()); + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + + let project_b = client_b.build_remote_project(project_id, cx_b).await; + cx_a.background_executor.run_until_parked(); + + assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); + + let workspace_b = + cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx)); + let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b); + + let editor_b = workspace_b + .update(cx_b, |workspace, cx| { + workspace.open_path((worktree_id, "b.txt"), None, true, cx) + }) + .unwrap() + .await + .unwrap() + .downcast::() + .unwrap(); + + //TODO: focus + assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx))); + editor_b.update(cx_b, |editor, cx| editor.insert("X", cx)); + //todo(is_edited) + // assert!(workspace_b.is_edited(cx_b)); + + // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared. + server.forbid_connections(); + server.disconnect_client(client_a.peer_id().unwrap()); + cx_a.background_executor + .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT); + + project_a.read_with(cx_a, |project, _| project.collaborators().is_empty()); + + project_a.read_with(cx_a, |project, _| assert!(!project.is_shared())); + + project_b.read_with(cx_b, |project, _| project.is_read_only()); + + assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared())); + + // Ensure client B's edited state is reset and that the whole window is blurred. + + workspace_b + .update(cx_b, |_, cx| { + assert_eq!(cx.focused(), None); + }) + .unwrap(); + // assert!(!workspace_b.is_edited(cx_b)); + + // Ensure client B is not prompted to save edits when closing window after disconnecting. + let can_close = workspace_b + .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx)) + .unwrap() + .await + .unwrap(); + assert!(can_close); + + // Allow client A to reconnect to the server. + server.allow_connections(); + cx_a.background_executor.advance_clock(RECEIVE_TIMEOUT); + + // Client B calls client A again after they reconnected. + let active_call_b = cx_b.read(ActiveCall::global); + active_call_b + .update(cx_b, |call, cx| { + call.invite(client_a.user_id().unwrap(), None, cx) + }) + .await + .unwrap(); + cx_a.background_executor.run_until_parked(); + active_call_a + .update(cx_a, |call, cx| call.accept_incoming(cx)) + .await + .unwrap(); + + active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + + // Drop client A's connection again. We should still unshare it successfully. + server.forbid_connections(); + server.disconnect_client(client_a.peer_id().unwrap()); + cx_a.background_executor + .advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT); + + project_a.read_with(cx_a, |project, _| assert!(!project.is_shared())); +} + +#[gpui::test] +async fn test_newline_above_or_below_does_not_move_guest_cursor( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let executor = cx_a.executor(); + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + + client_a + .fs() + .insert_tree("/dir", json!({ "a.txt": "Some text\n" })) + .await; + let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + + let project_b = client_b.build_remote_project(project_id, cx_b).await; + + // Open a buffer as client A + let buffer_a = project_a + .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .await + .unwrap(); + let window_a = cx_a.add_empty_window(); + let editor_a = + window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx)); + + let mut editor_cx_a = EditorTestContext { + cx: VisualTestContext::from_window(window_a, cx_a), + window: window_a.into(), + editor: editor_a, + assertion_cx: AssertionContextManager::new(), + }; + + let window_b = cx_b.add_empty_window(); + let mut cx_b = VisualTestContext::from_window(window_b, cx_b); + + // Open a buffer as client B + let buffer_b = project_b + .update(&mut cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .await + .unwrap(); + let editor_b = window_b.build_view(&mut cx_b, |cx| { + Editor::for_buffer(buffer_b, Some(project_b), cx) + }); + let mut editor_cx_b = EditorTestContext { + cx: cx_b, + window: window_b.into(), + editor: editor_b, + assertion_cx: AssertionContextManager::new(), + }; + + // Test newline above + editor_cx_a.set_selections_state(indoc! {" + Some textˇ + "}); + editor_cx_b.set_selections_state(indoc! {" + Some textˇ + "}); + editor_cx_a.update_editor(|editor, cx| editor.newline_above(&editor::NewlineAbove, cx)); + executor.run_until_parked(); + editor_cx_a.assert_editor_state(indoc! {" + ˇ + Some text + "}); + editor_cx_b.assert_editor_state(indoc! {" + + Some textˇ + "}); + + // Test newline below + editor_cx_a.set_selections_state(indoc! {" + + Some textˇ + "}); + editor_cx_b.set_selections_state(indoc! {" + + Some textˇ + "}); + editor_cx_a.update_editor(|editor, cx| editor.newline_below(&editor::NewlineBelow, cx)); + executor.run_until_parked(); + editor_cx_a.assert_editor_state(indoc! {" + + Some text + ˇ + "}); + editor_cx_b.assert_editor_state(indoc! {" + + Some textˇ + + "}); +} + +#[gpui::test(iterations = 10)] +async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + + // Set up a fake language server. + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string()]), + resolve_provider: Some(true), + ..Default::default() + }), + ..Default::default() + }, + ..Default::default() + })) + .await; + client_a.language_registry().add(Arc::new(language)); + + client_a + .fs() + .insert_tree( + "/a", + json!({ + "main.rs": "fn main() { a }", + "other.rs": "", + }), + ) + .await; + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + let project_b = client_b.build_remote_project(project_id, cx_b).await; + + // Open a file in an editor as the guest. + let buffer_b = project_b + .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .await + .unwrap(); + let window_b = cx_b.add_empty_window(); + let editor_b = window_b.build_view(cx_b, |cx| { + Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx) + }); + + let fake_language_server = fake_language_servers.next().await.unwrap(); + cx_a.background_executor.run_until_parked(); + + buffer_b.read_with(cx_b, |buffer, _| { + assert!(!buffer.completion_triggers().is_empty()) + }); + + let mut cx_b = VisualTestContext::from_window(window_b, cx_b); + + // Type a completion trigger character as the guest. + editor_b.update(&mut cx_b, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input(".", cx); + editor_b.focus_handle(cx).focus(cx); + }); + + // Receive a completion request as the host's language server. + // Return some completions from the host's language server. + cx_a.executor().start_waiting(); + fake_language_server + .handle_request::(|params, _| async move { + assert_eq!( + params.text_document_position.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + assert_eq!( + params.text_document_position.position, + lsp::Position::new(0, 14), + ); + + Ok(Some(lsp::CompletionResponse::Array(vec![ + lsp::CompletionItem { + label: "first_method(…)".into(), + detail: Some("fn(&mut self, B) -> C".into()), + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + new_text: "first_method($1)".to_string(), + range: lsp::Range::new( + lsp::Position::new(0, 14), + lsp::Position::new(0, 14), + ), + })), + insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), + ..Default::default() + }, + lsp::CompletionItem { + label: "second_method(…)".into(), + detail: Some("fn(&mut self, C) -> D".into()), + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + new_text: "second_method()".to_string(), + range: lsp::Range::new( + lsp::Position::new(0, 14), + lsp::Position::new(0, 14), + ), + })), + insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), + ..Default::default() + }, + ]))) + }) + .next() + .await + .unwrap(); + cx_a.executor().finish_waiting(); + + // Open the buffer on the host. + let buffer_a = project_a + .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .await + .unwrap(); + cx_a.executor().run_until_parked(); + + buffer_a.read_with(cx_a, |buffer, _| { + assert_eq!(buffer.text(), "fn main() { a. }") + }); + + // Confirm a completion on the guest. + + editor_b.update(&mut cx_b, |editor, cx| { + assert!(editor.context_menu_visible()); + editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx); + assert_eq!(editor.text(cx), "fn main() { a.first_method() }"); + }); + + // Return a resolved completion from the host's language server. + // The resolved completion has an additional text edit. + fake_language_server.handle_request::( + |params, _| async move { + assert_eq!(params.label, "first_method(…)"); + Ok(lsp::CompletionItem { + label: "first_method(…)".into(), + detail: Some("fn(&mut self, B) -> C".into()), + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + new_text: "first_method($1)".to_string(), + range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)), + })), + additional_text_edits: Some(vec![lsp::TextEdit { + new_text: "use d::SomeTrait;\n".to_string(), + range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)), + }]), + insert_text_format: Some(lsp::InsertTextFormat::SNIPPET), + ..Default::default() + }) + }, + ); + + // The additional edit is applied. + cx_a.executor().run_until_parked(); + + buffer_a.read_with(cx_a, |buffer, _| { + assert_eq!( + buffer.text(), + "use d::SomeTrait;\nfn main() { a.first_method() }" + ); + }); + + buffer_b.read_with(&mut cx_b, |buffer, _| { + assert_eq!( + buffer.text(), + "use d::SomeTrait;\nfn main() { a.first_method() }" + ); + }); +} + +#[gpui::test(iterations = 10)] +async fn test_collaborating_with_code_actions( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + // + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + + cx_b.update(editor::init); + + // Set up a fake language server. + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await; + client_a.language_registry().add(Arc::new(language)); + + client_a + .fs() + .insert_tree( + "/a", + json!({ + "main.rs": "mod other;\nfn main() { let foo = other::foo(); }", + "other.rs": "pub fn foo() -> usize { 4 }", + }), + ) + .await; + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + + // Join the project as client B. + let project_b = client_b.build_remote_project(project_id, cx_b).await; + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + let editor_b = workspace_b + .update(cx_b, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let mut fake_language_server = fake_language_servers.next().await.unwrap(); + let mut requests = fake_language_server + .handle_request::(|params, _| async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + assert_eq!(params.range.start, lsp::Position::new(0, 0)); + assert_eq!(params.range.end, lsp::Position::new(0, 0)); + Ok(None) + }); + cx_a.background_executor + .advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2); + requests.next().await; + + // Move cursor to a location that contains code actions. + editor_b.update(cx_b, |editor, cx| { + editor.change_selections(None, cx, |s| { + s.select_ranges([Point::new(1, 31)..Point::new(1, 31)]) + }); + }); + cx_b.focus_view(&editor_b); + + let mut requests = fake_language_server + .handle_request::(|params, _| async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + assert_eq!(params.range.start, lsp::Position::new(1, 31)); + assert_eq!(params.range.end, lsp::Position::new(1, 31)); + + Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction( + lsp::CodeAction { + title: "Inline into all callers".to_string(), + edit: Some(lsp::WorkspaceEdit { + changes: Some( + [ + ( + lsp::Url::from_file_path("/a/main.rs").unwrap(), + vec![lsp::TextEdit::new( + lsp::Range::new( + lsp::Position::new(1, 22), + lsp::Position::new(1, 34), + ), + "4".to_string(), + )], + ), + ( + lsp::Url::from_file_path("/a/other.rs").unwrap(), + vec![lsp::TextEdit::new( + lsp::Range::new( + lsp::Position::new(0, 0), + lsp::Position::new(0, 27), + ), + "".to_string(), + )], + ), + ] + .into_iter() + .collect(), + ), + ..Default::default() + }), + data: Some(json!({ + "codeActionParams": { + "range": { + "start": {"line": 1, "column": 31}, + "end": {"line": 1, "column": 31}, + } + } + })), + ..Default::default() + }, + )])) + }); + cx_a.background_executor + .advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2); + requests.next().await; + + // Toggle code actions and wait for them to display. + editor_b.update(cx_b, |editor, cx| { + editor.toggle_code_actions( + &ToggleCodeActions { + deployed_from_indicator: false, + }, + cx, + ); + }); + cx_a.background_executor.run_until_parked(); + + editor_b.update(cx_b, |editor, _| assert!(editor.context_menu_visible())); + + fake_language_server.remove_request_handler::(); + + // Confirming the code action will trigger a resolve request. + let confirm_action = editor_b + .update(cx_b, |editor, cx| { + Editor::confirm_code_action(editor, &ConfirmCodeAction { item_ix: Some(0) }, cx) + }) + .unwrap(); + fake_language_server.handle_request::( + |_, _| async move { + Ok(lsp::CodeAction { + title: "Inline into all callers".to_string(), + edit: Some(lsp::WorkspaceEdit { + changes: Some( + [ + ( + lsp::Url::from_file_path("/a/main.rs").unwrap(), + vec![lsp::TextEdit::new( + lsp::Range::new( + lsp::Position::new(1, 22), + lsp::Position::new(1, 34), + ), + "4".to_string(), + )], + ), + ( + lsp::Url::from_file_path("/a/other.rs").unwrap(), + vec![lsp::TextEdit::new( + lsp::Range::new( + lsp::Position::new(0, 0), + lsp::Position::new(0, 27), + ), + "".to_string(), + )], + ), + ] + .into_iter() + .collect(), + ), + ..Default::default() + }), + ..Default::default() + }) + }, + ); + + // After the action is confirmed, an editor containing both modified files is opened. + confirm_action.await.unwrap(); + + let code_action_editor = workspace_b.update(cx_b, |workspace, cx| { + workspace + .active_item(cx) + .unwrap() + .downcast::() + .unwrap() + }); + code_action_editor.update(cx_b, |editor, cx| { + assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n"); + editor.undo(&Undo, cx); + assert_eq!( + editor.text(cx), + "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }" + ); + editor.redo(&Redo, cx); + assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n"); + }); +} + +#[gpui::test(iterations = 10)] +async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let mut server = TestServer::start(cx_a.executor()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + + cx_b.update(editor::init); + + // Set up a fake language server. + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions { + prepare_provider: Some(true), + work_done_progress_options: Default::default(), + })), + ..Default::default() + }, + ..Default::default() + })) + .await; + client_a.language_registry().add(Arc::new(language)); + + client_a + .fs() + .insert_tree( + "/dir", + json!({ + "one.rs": "const ONE: usize = 1;", + "two.rs": "const TWO: usize = one::ONE + one::ONE;" + }), + ) + .await; + let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + let project_b = client_b.build_remote_project(project_id, cx_b).await; + + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + let editor_b = workspace_b + .update(cx_b, |workspace, cx| { + workspace.open_path((worktree_id, "one.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + let fake_language_server = fake_language_servers.next().await.unwrap(); + + // Move cursor to a location that can be renamed. + let prepare_rename = editor_b.update(cx_b, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([7..7])); + editor.rename(&Rename, cx).unwrap() + }); + + fake_language_server + .handle_request::(|params, _| async move { + assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs"); + assert_eq!(params.position, lsp::Position::new(0, 7)); + Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new( + lsp::Position::new(0, 6), + lsp::Position::new(0, 9), + )))) + }) + .next() + .await + .unwrap(); + prepare_rename.await.unwrap(); + editor_b.update(cx_b, |editor, cx| { + use editor::ToOffset; + let rename = editor.pending_rename().unwrap(); + let buffer = editor.buffer().read(cx).snapshot(cx); + assert_eq!( + rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer), + 6..9 + ); + rename.editor.update(cx, |rename_editor, cx| { + rename_editor.buffer().update(cx, |rename_buffer, cx| { + rename_buffer.edit([(0..3, "THREE")], None, cx); + }); + }); + }); + + let confirm_rename = editor_b.update(cx_b, |editor, cx| { + Editor::confirm_rename(editor, &ConfirmRename, cx).unwrap() + }); + fake_language_server + .handle_request::(|params, _| async move { + assert_eq!( + params.text_document_position.text_document.uri.as_str(), + "file:///dir/one.rs" + ); + assert_eq!( + params.text_document_position.position, + lsp::Position::new(0, 6) + ); + assert_eq!(params.new_name, "THREE"); + Ok(Some(lsp::WorkspaceEdit { + changes: Some( + [ + ( + lsp::Url::from_file_path("/dir/one.rs").unwrap(), + vec![lsp::TextEdit::new( + lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)), + "THREE".to_string(), + )], + ), + ( + lsp::Url::from_file_path("/dir/two.rs").unwrap(), + vec![ + lsp::TextEdit::new( + lsp::Range::new( + lsp::Position::new(0, 24), + lsp::Position::new(0, 27), + ), + "THREE".to_string(), + ), + lsp::TextEdit::new( + lsp::Range::new( + lsp::Position::new(0, 35), + lsp::Position::new(0, 38), + ), + "THREE".to_string(), + ), + ], + ), + ] + .into_iter() + .collect(), + ), + ..Default::default() + })) + }) + .next() + .await + .unwrap(); + confirm_rename.await.unwrap(); + + let rename_editor = workspace_b.update(cx_b, |workspace, cx| { + workspace.active_item_as::(cx).unwrap() + }); + + rename_editor.update(cx_b, |editor, cx| { + assert_eq!( + editor.text(cx), + "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;" + ); + editor.undo(&Undo, cx); + assert_eq!( + editor.text(cx), + "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;" + ); + editor.redo(&Redo, cx); + assert_eq!( + editor.text(cx), + "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;" + ); + }); + + // Ensure temporary rename edits cannot be undone/redone. + editor_b.update(cx_b, |editor, cx| { + editor.undo(&Undo, cx); + assert_eq!(editor.text(cx), "const ONE: usize = 1;"); + editor.undo(&Undo, cx); + assert_eq!(editor.text(cx), "const ONE: usize = 1;"); + editor.redo(&Redo, cx); + assert_eq!(editor.text(cx), "const THREE: usize = 1;"); + }) +} + +#[gpui::test(iterations = 10)] +async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { + let mut server = TestServer::start(cx_a.executor()).await; + let executor = cx_a.executor(); + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + + cx_b.update(editor::init); + + // Set up a fake language server. + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + name: "the-language-server", + ..Default::default() + })) + .await; + client_a.language_registry().add(Arc::new(language)); + + client_a + .fs() + .insert_tree( + "/dir", + json!({ + "main.rs": "const ONE: usize = 1;", + }), + ) + .await; + let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; + + let _buffer_a = project_a + .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .await + .unwrap(); + + let fake_language_server = fake_language_servers.next().await.unwrap(); + fake_language_server.start_progress("the-token").await; + fake_language_server.notify::(lsp::ProgressParams { + token: lsp::NumberOrString::String("the-token".to_string()), + value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report( + lsp::WorkDoneProgressReport { + message: Some("the-message".to_string()), + ..Default::default() + }, + )), + }); + executor.run_until_parked(); + + project_a.read_with(cx_a, |project, _| { + let status = project.language_server_statuses().next().unwrap(); + assert_eq!(status.name, "the-language-server"); + assert_eq!(status.pending_work.len(), 1); + assert_eq!( + status.pending_work["the-token"].message.as_ref().unwrap(), + "the-message" + ); + }); + + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + executor.run_until_parked(); + let project_b = client_b.build_remote_project(project_id, cx_b).await; + + project_b.read_with(cx_b, |project, _| { + let status = project.language_server_statuses().next().unwrap(); + assert_eq!(status.name, "the-language-server"); + }); + + fake_language_server.notify::(lsp::ProgressParams { + token: lsp::NumberOrString::String("the-token".to_string()), + value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report( + lsp::WorkDoneProgressReport { + message: Some("the-message-2".to_string()), + ..Default::default() + }, + )), + }); + executor.run_until_parked(); + + project_a.read_with(cx_a, |project, _| { + let status = project.language_server_statuses().next().unwrap(); + assert_eq!(status.name, "the-language-server"); + assert_eq!(status.pending_work.len(), 1); + assert_eq!( + status.pending_work["the-token"].message.as_ref().unwrap(), + "the-message-2" + ); + }); + + project_b.read_with(cx_b, |project, _| { + let status = project.language_server_statuses().next().unwrap(); + assert_eq!(status.name, "the-language-server"); + assert_eq!(status.pending_work.len(), 1); + assert_eq!( + status.pending_work["the-token"].message.as_ref().unwrap(), + "the-message-2" + ); + }); +} + +#[gpui::test(iterations = 10)] +async fn test_share_project( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, + cx_c: &mut TestAppContext, +) { + let executor = cx_a.executor(); + let window_b = cx_b.add_empty_window(); + let mut server = TestServer::start(executor.clone()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let client_c = server.create_client(cx_c, "user_c").await; + server + .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + let active_call_c = cx_c.read(ActiveCall::global); + + client_a + .fs() + .insert_tree( + "/a", + json!({ + ".gitignore": "ignored-dir", + "a.txt": "a-contents", + "b.txt": "b-contents", + "ignored-dir": { + "c.txt": "", + "d.txt": "", + } + }), + ) + .await; + + // Invite client B to collaborate on a project + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + active_call_a + .update(cx_a, |call, cx| { + call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx) + }) + .await + .unwrap(); + + // Join that project as client B + + let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming()); + executor.run_until_parked(); + let call = incoming_call_b.borrow().clone().unwrap(); + assert_eq!(call.calling_user.github_login, "user_a"); + let initial_project = call.initial_project.unwrap(); + active_call_b + .update(cx_b, |call, cx| call.accept_incoming(cx)) + .await + .unwrap(); + let client_b_peer_id = client_b.peer_id().unwrap(); + let project_b = client_b + .build_remote_project(initial_project.id, cx_b) + .await; + + let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id()); + + executor.run_until_parked(); + + project_a.read_with(cx_a, |project, _| { + let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap(); + assert_eq!(client_b_collaborator.replica_id, replica_id_b); + }); + + project_b.read_with(cx_b, |project, cx| { + let worktree = project.worktrees().next().unwrap().read(cx); + assert_eq!( + worktree.paths().map(AsRef::as_ref).collect::>(), + [ + Path::new(".gitignore"), + Path::new("a.txt"), + Path::new("b.txt"), + Path::new("ignored-dir"), + ] + ); + }); + + project_b + .update(cx_b, |project, cx| { + let worktree = project.worktrees().next().unwrap(); + let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap(); + project.expand_entry(worktree_id, entry.id, cx).unwrap() + }) + .await + .unwrap(); + + project_b.read_with(cx_b, |project, cx| { + let worktree = project.worktrees().next().unwrap().read(cx); + assert_eq!( + worktree.paths().map(AsRef::as_ref).collect::>(), + [ + Path::new(".gitignore"), + Path::new("a.txt"), + Path::new("b.txt"), + Path::new("ignored-dir"), + Path::new("ignored-dir/c.txt"), + Path::new("ignored-dir/d.txt"), + ] + ); + }); + + // Open the same file as client B and client A. + let buffer_b = project_b + .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx)) + .await + .unwrap(); + + buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents")); + + project_a.read_with(cx_a, |project, cx| { + assert!(project.has_open_buffer((worktree_id, "b.txt"), cx)) + }); + let buffer_a = project_a + .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx)) + .await + .unwrap(); + + let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx)); + + // Client A sees client B's selection + executor.run_until_parked(); + + buffer_a.read_with(cx_a, |buffer, _| { + buffer + .snapshot() + .remote_selections_in_range(text::Anchor::MIN..text::Anchor::MAX) + .count() + == 1 + }); + + // Edit the buffer as client B and see that edit as client A. + let mut cx_b = VisualTestContext::from_window(window_b, cx_b); + editor_b.update(&mut cx_b, |editor, cx| editor.handle_input("ok, ", cx)); + executor.run_until_parked(); + + buffer_a.read_with(cx_a, |buffer, _| { + assert_eq!(buffer.text(), "ok, b-contents") + }); + + // Client B can invite client C on a project shared by client A. + active_call_b + .update(&mut cx_b, |call, cx| { + call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx) + }) + .await + .unwrap(); + + let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming()); + executor.run_until_parked(); + let call = incoming_call_c.borrow().clone().unwrap(); + assert_eq!(call.calling_user.github_login, "user_b"); + let initial_project = call.initial_project.unwrap(); + active_call_c + .update(cx_c, |call, cx| call.accept_incoming(cx)) + .await + .unwrap(); + let _project_c = client_c + .build_remote_project(initial_project.id, cx_c) + .await; + + // Client B closes the editor, and client A sees client B's selections removed. + cx_b.update(move |_| drop(editor_b)); + executor.run_until_parked(); + + buffer_a.read_with(cx_a, |buffer, _| { + buffer + .snapshot() + .remote_selections_in_range(text::Anchor::MIN..text::Anchor::MAX) + .count() + == 0 + }); +} + +#[gpui::test(iterations = 10)] +async fn test_on_input_format_from_host_to_guest( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let mut server = TestServer::start(cx_a.executor()).await; + let executor = cx_a.executor(); + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + + // Set up a fake language server. + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions { + first_trigger_character: ":".to_string(), + more_trigger_character: Some(vec![">".to_string()]), + }), + ..Default::default() + }, + ..Default::default() + })) + .await; + client_a.language_registry().add(Arc::new(language)); + + client_a + .fs() + .insert_tree( + "/a", + json!({ + "main.rs": "fn main() { a }", + "other.rs": "// Test file", + }), + ) + .await; + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + let project_b = client_b.build_remote_project(project_id, cx_b).await; + + // Open a file in an editor as the host. + let buffer_a = project_a + .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .await + .unwrap(); + let window_a = cx_a.add_empty_window(); + let editor_a = window_a + .update(cx_a, |_, cx| { + cx.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)) + }) + .unwrap(); + + let fake_language_server = fake_language_servers.next().await.unwrap(); + executor.run_until_parked(); + + // Receive an OnTypeFormatting request as the host's language server. + // Return some formattings from the host's language server. + fake_language_server.handle_request::( + |params, _| async move { + assert_eq!( + params.text_document_position.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + assert_eq!( + params.text_document_position.position, + lsp::Position::new(0, 14), + ); + + Ok(Some(vec![lsp::TextEdit { + new_text: "~<".to_string(), + range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)), + }])) + }, + ); + + // Open the buffer on the guest and see that the formattings worked + let buffer_b = project_b + .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .await + .unwrap(); + + let mut cx_a = VisualTestContext::from_window(window_a, cx_a); + // Type a on type formatting trigger character as the guest. + cx_a.focus_view(&editor_a); + editor_a.update(&mut cx_a, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input(">", cx); + }); + + executor.run_until_parked(); + + buffer_b.read_with(cx_b, |buffer, _| { + assert_eq!(buffer.text(), "fn main() { a>~< }") + }); + + // Undo should remove LSP edits first + editor_a.update(&mut cx_a, |editor, cx| { + assert_eq!(editor.text(cx), "fn main() { a>~< }"); + editor.undo(&Undo, cx); + assert_eq!(editor.text(cx), "fn main() { a> }"); + }); + executor.run_until_parked(); + + buffer_b.read_with(cx_b, |buffer, _| { + assert_eq!(buffer.text(), "fn main() { a> }") + }); + + editor_a.update(&mut cx_a, |editor, cx| { + assert_eq!(editor.text(cx), "fn main() { a> }"); + editor.undo(&Undo, cx); + assert_eq!(editor.text(cx), "fn main() { a }"); + }); + executor.run_until_parked(); + + buffer_b.read_with(cx_b, |buffer, _| { + assert_eq!(buffer.text(), "fn main() { a }") + }); +} + +#[gpui::test(iterations = 10)] +async fn test_on_input_format_from_guest_to_host( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let mut server = TestServer::start(cx_a.executor()).await; + let executor = cx_a.executor(); + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + + // Set up a fake language server. + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions { + first_trigger_character: ":".to_string(), + more_trigger_character: Some(vec![">".to_string()]), + }), + ..Default::default() + }, + ..Default::default() + })) + .await; + client_a.language_registry().add(Arc::new(language)); + + client_a + .fs() + .insert_tree( + "/a", + json!({ + "main.rs": "fn main() { a }", + "other.rs": "// Test file", + }), + ) + .await; + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + let project_b = client_b.build_remote_project(project_id, cx_b).await; + + // Open a file in an editor as the guest. + let buffer_b = project_b + .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .await + .unwrap(); + let window_b = cx_b.add_empty_window(); + let editor_b = window_b.build_view(cx_b, |cx| { + Editor::for_buffer(buffer_b, Some(project_b.clone()), cx) + }); + + let fake_language_server = fake_language_servers.next().await.unwrap(); + executor.run_until_parked(); + let mut cx_b = VisualTestContext::from_window(window_b, cx_b); + // Type a on type formatting trigger character as the guest. + cx_b.focus_view(&editor_b); + editor_b.update(&mut cx_b, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input(":", cx); + }); + + // Receive an OnTypeFormatting request as the host's language server. + // Return some formattings from the host's language server. + executor.start_waiting(); + fake_language_server + .handle_request::(|params, _| async move { + assert_eq!( + params.text_document_position.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + assert_eq!( + params.text_document_position.position, + lsp::Position::new(0, 14), + ); + + Ok(Some(vec![lsp::TextEdit { + new_text: "~:".to_string(), + range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)), + }])) + }) + .next() + .await + .unwrap(); + executor.finish_waiting(); + + // Open the buffer on the host and see that the formattings worked + let buffer_a = project_a + .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) + .await + .unwrap(); + executor.run_until_parked(); + + buffer_a.read_with(cx_a, |buffer, _| { + assert_eq!(buffer.text(), "fn main() { a:~: }") + }); + + // Undo should remove LSP edits first + editor_b.update(&mut cx_b, |editor, cx| { + assert_eq!(editor.text(cx), "fn main() { a:~: }"); + editor.undo(&Undo, cx); + assert_eq!(editor.text(cx), "fn main() { a: }"); + }); + executor.run_until_parked(); + + buffer_a.read_with(cx_a, |buffer, _| { + assert_eq!(buffer.text(), "fn main() { a: }") + }); + + editor_b.update(&mut cx_b, |editor, cx| { + assert_eq!(editor.text(cx), "fn main() { a: }"); + editor.undo(&Undo, cx); + assert_eq!(editor.text(cx), "fn main() { a }"); + }); + executor.run_until_parked(); + + buffer_a.read_with(cx_a, |buffer, _| { + assert_eq!(buffer.text(), "fn main() { a }") + }); +} + +#[gpui::test(iterations = 10)] +async fn test_mutual_editor_inlay_hint_cache_update( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let mut server = TestServer::start(cx_a.executor()).await; + let executor = cx_a.executor(); + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + + cx_a.update(editor::init); + cx_b.update(editor::init); + + cx_a.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: true, + show_parameter_hints: false, + show_other_hints: true, + }) + }); + }); + }); + cx_b.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: true, + show_parameter_hints: false, + show_other_hints: true, + }) + }); + }); + }); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, + ..Default::default() + })) + .await; + let language = Arc::new(language); + client_a.language_registry().add(Arc::clone(&language)); + client_b.language_registry().add(language); + + // Client A opens a project. + client_a + .fs() + .insert_tree( + "/a", + json!({ + "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out", + "other.rs": "// Test file", + }), + ) + .await; + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + active_call_a + .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) + .await + .unwrap(); + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + + // Client B joins the project + let project_b = client_b.build_remote_project(project_id, cx_b).await; + active_call_b + .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) + .await + .unwrap(); + + let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); + executor.start_waiting(); + + // The host opens a rust file. + let _buffer_a = project_a + .update(cx_a, |project, cx| { + project.open_local_buffer("/a/main.rs", cx) + }) + .await + .unwrap(); + let fake_language_server = fake_language_servers.next().await.unwrap(); + let editor_a = workspace_a + .update(cx_a, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + // Set up the language server to return an additional inlay hint on each request. + let edits_made = Arc::new(AtomicUsize::new(0)); + let closure_edits_made = Arc::clone(&edits_made); + fake_language_server + .handle_request::(move |params, _| { + let task_edits_made = Arc::clone(&closure_edits_made); + async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + let edits_made = task_edits_made.load(atomic::Ordering::Acquire); + Ok(Some(vec![lsp::InlayHint { + position: lsp::Position::new(0, edits_made as u32), + label: lsp::InlayHintLabel::String(edits_made.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }])) + } + }) + .next() + .await + .unwrap(); + + executor.run_until_parked(); + + let initial_edit = edits_made.load(atomic::Ordering::Acquire); + editor_a.update(cx_a, |editor, _| { + assert_eq!( + vec![initial_edit.to_string()], + extract_hint_labels(editor), + "Host should get its first hints when opens an editor" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.version(), + 1, + "Host editor update the cache version after every cache/view change", + ); + }); + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + let editor_b = workspace_b + .update(cx_b, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + executor.run_until_parked(); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec![initial_edit.to_string()], + extract_hint_labels(editor), + "Client should get its first hints when opens an editor" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.version(), + 1, + "Guest editor update the cache version after every cache/view change" + ); + }); + + let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1; + editor_b.update(cx_b, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone())); + editor.handle_input(":", cx); + }); + cx_b.focus_view(&editor_b); + + executor.run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert_eq!( + vec![after_client_edit.to_string()], + extract_hint_labels(editor), + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.version(), 2); + }); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec![after_client_edit.to_string()], + extract_hint_labels(editor), + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.version(), 2); + }); + + let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1; + editor_a.update(cx_a, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input("a change to increment both buffers' versions", cx); + }); + cx_a.focus_view(&editor_a); + + executor.run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert_eq!( + vec![after_host_edit.to_string()], + extract_hint_labels(editor), + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.version(), 3); + }); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec![after_host_edit.to_string()], + extract_hint_labels(editor), + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.version(), 3); + }); + + let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1; + fake_language_server + .request::(()) + .await + .expect("inlay refresh request failed"); + + executor.run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert_eq!( + vec![after_special_edit_for_refresh.to_string()], + extract_hint_labels(editor), + "Host should react to /refresh LSP request" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.version(), + 4, + "Host should accepted all edits and bump its cache version every time" + ); + }); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec![after_special_edit_for_refresh.to_string()], + extract_hint_labels(editor), + "Guest should get a /refresh LSP request propagated by host" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.version(), + 4, + "Guest should accepted all edits and bump its cache version every time" + ); + }); +} + +#[gpui::test(iterations = 10)] +async fn test_inlay_hint_refresh_is_forwarded( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + let mut server = TestServer::start(cx_a.executor()).await; + let executor = cx_a.executor(); + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + + cx_a.update(editor::init); + cx_b.update(editor::init); + + cx_a.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: false, + show_type_hints: false, + show_parameter_hints: false, + show_other_hints: false, + }) + }); + }); + }); + cx_b.update(|cx| { + cx.update_global(|store: &mut SettingsStore, cx| { + store.update_user_settings::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: true, + show_parameter_hints: true, + show_other_hints: true, + }) + }); + }); + }); + + let mut language = Language::new( + LanguageConfig { + name: "Rust".into(), + path_suffixes: vec!["rs".to_string()], + ..Default::default() + }, + Some(tree_sitter_rust::language()), + ); + let mut fake_language_servers = language + .set_fake_lsp_adapter(Arc::new(FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + inlay_hint_provider: Some(lsp::OneOf::Left(true)), + ..Default::default() + }, + ..Default::default() + })) + .await; + let language = Arc::new(language); + client_a.language_registry().add(Arc::clone(&language)); + client_b.language_registry().add(language); + + client_a + .fs() + .insert_tree( + "/a", + json!({ + "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out", + "other.rs": "// Test file", + }), + ) + .await; + let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + active_call_a + .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) + .await + .unwrap(); + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + + let project_b = client_b.build_remote_project(project_id, cx_b).await; + active_call_b + .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) + .await + .unwrap(); + + let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + + cx_a.background_executor.start_waiting(); + + let editor_a = workspace_a + .update(cx_a, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let editor_b = workspace_b + .update(cx_b, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let other_hints = Arc::new(AtomicBool::new(false)); + let fake_language_server = fake_language_servers.next().await.unwrap(); + let closure_other_hints = Arc::clone(&other_hints); + fake_language_server + .handle_request::(move |params, _| { + let task_other_hints = Arc::clone(&closure_other_hints); + async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + let other_hints = task_other_hints.load(atomic::Ordering::Acquire); + let character = if other_hints { 0 } else { 2 }; + let label = if other_hints { + "other hint" + } else { + "initial hint" + }; + Ok(Some(vec![lsp::InlayHint { + position: lsp::Position::new(0, character), + label: lsp::InlayHintLabel::String(label.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }])) + } + }) + .next() + .await + .unwrap(); + executor.finish_waiting(); + + executor.run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert!( + extract_hint_labels(editor).is_empty(), + "Host should get no hints due to them turned off" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.version(), + 0, + "Turned off hints should not generate version updates" + ); + }); + + executor.run_until_parked(); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec!["initial hint".to_string()], + extract_hint_labels(editor), + "Client should get its first hints when opens an editor" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.version(), + 1, + "Should update cache verison after first hints" + ); + }); + + other_hints.fetch_or(true, atomic::Ordering::Release); + fake_language_server + .request::(()) + .await + .expect("inlay refresh request failed"); + executor.run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert!( + extract_hint_labels(editor).is_empty(), + "Host should get nop hints due to them turned off, even after the /refresh" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.version(), + 0, + "Turned off hints should not generate version updates, again" + ); + }); + + executor.run_until_parked(); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec!["other hint".to_string()], + extract_hint_labels(editor), + "Guest should get a /refresh LSP request propagated by host despite host hints are off" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.version(), + 2, + "Guest should accepted all edits and bump its cache version every time" + ); + }); +} + +fn extract_hint_labels(editor: &Editor) -> Vec { + let mut labels = Vec::new(); + for hint in editor.inlay_hint_cache().hints() { + match hint.label { + project::InlayHintLabel::String(s) => labels.push(s), + _ => unreachable!(), + } + } + labels +} From 5e3d4885bff7dbebd683a565899983d0b39a393f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jan 2024 13:04:17 -0800 Subject: [PATCH 33/67] Fix some bugs in keymap handling (#3895) - `base_keymap` setting was not respected, now it is - without a `~/.config/zed/keymap.json` file, we would fail to load the *default* keymap Co-authored-by: Marshall --- crates/command_palette/src/command_palette.rs | 16 ++++- crates/gpui/src/app.rs | 16 +++-- crates/settings/src/settings_file.rs | 14 +--- crates/zed/src/zed.rs | 66 ++++++++++++------- 4 files changed, 71 insertions(+), 41 deletions(-) diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index b7a1dbfd3dd48abc08a05e6a151a24fa3ef4af83..bbc2cd412305aff870f712187c4b7e13d916303d 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -370,6 +370,7 @@ mod tests { use gpui::TestAppContext; use language::Point; use project::Project; + use settings::KeymapFile; use workspace::{AppState, Workspace}; #[test] @@ -503,7 +504,20 @@ mod tests { workspace::init(app_state.clone(), cx); init(cx); Project::init_settings(cx); - settings::load_default_keymap(cx); + KeymapFile::parse( + r#"[ + { + "bindings": { + "cmd-n": "workspace::NewFile", + "enter": "menu::Confirm", + "cmd-shift-p": "command_palette::Toggle" + } + } + ]"#, + ) + .unwrap() + .add_to_cx(cx) + .unwrap(); app_state }) } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index f8da622b53bcc1beb4e1e33c3f3cd860696801b7..4ad9540043e1058ee9ab9f5e9df9ef9bbce92057 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -327,6 +327,7 @@ impl AppContext { pub fn refresh(&mut self) { self.pending_effects.push_back(Effect::Refresh); } + pub(crate) fn update(&mut self, update: impl FnOnce(&mut Self) -> R) -> R { self.pending_updates += 1; let result = update(self); @@ -840,10 +841,12 @@ impl AppContext { /// Update the global of the given type with a closure. Unlike `global_mut`, this method provides /// your closure with mutable access to the `AppContext` and the global simultaneously. pub fn update_global(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R { - let mut global = self.lease_global::(); - let result = f(&mut global, self); - self.end_global_lease(global); - result + self.update(|cx| { + let mut global = cx.lease_global::(); + let result = f(&mut global, cx); + cx.end_global_lease(global); + result + }) } /// Register a callback to be invoked when a global of the given type is updated. @@ -941,6 +944,11 @@ impl AppContext { self.pending_effects.push_back(Effect::Refresh); } + pub fn clear_key_bindings(&mut self) { + self.keymap.lock().clear(); + self.pending_effects.push_back(Effect::Refresh); + } + /// Register a global listener for actions invoked via the keyboard. pub fn on_action(&mut self, listener: impl Fn(&A, &mut Self) + 'static) { self.global_action_listeners diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index 590079c51b52fe77a2c83ec4a862b27a0202ad1a..3a43e3f9dd6a22a5c6e8d74dece4a7982231af4e 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -1,4 +1,4 @@ -use crate::{settings_store::SettingsStore, KeymapFile, Settings}; +use crate::{settings_store::SettingsStore, Settings}; use anyhow::Result; use fs::Fs; use futures::{channel::mpsc, StreamExt}; @@ -77,7 +77,6 @@ pub fn handle_settings_file_changes( }); cx.spawn(move |mut cx| async move { while let Some(user_settings_content) = user_settings_file_rx.next().await { - eprintln!("settings file changed"); let result = cx.update_global(|store: &mut SettingsStore, cx| { store .set_user_settings(&user_settings_content, cx) @@ -121,14 +120,3 @@ pub fn update_settings_file( }) .detach_and_log_err(cx); } - -pub fn load_default_keymap(cx: &mut AppContext) { - for path in ["keymaps/default.json", "keymaps/vim.json"] { - KeymapFile::load_asset(path, cx).unwrap(); - } - - // todo!() - // if let Some(asset_path) = settings::get::(cx).asset_path() { - // KeymapFile::load_asset(asset_path, cx).unwrap(); - // } -} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index fb85b1fc012085720dd7e11c1863cefcae84723a..fea84c296419c2a7f41a538ec026cdececeee99f 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -18,11 +18,11 @@ pub use only_instance::*; pub use open_listener::*; use anyhow::{anyhow, Context as _}; -use futures::{channel::mpsc, StreamExt}; +use futures::{channel::mpsc, select_biased, StreamExt}; use project_panel::ProjectPanel; use quick_action_bar::QuickActionBar; use search::project_search::ProjectSearchBar; -use settings::{initial_local_settings_content, load_default_keymap, KeymapFile, Settings}; +use settings::{initial_local_settings_content, KeymapFile, Settings, SettingsStore}; use std::{borrow::Cow, ops::Deref, sync::Arc}; use terminal_view::terminal_panel::TerminalPanel; use util::{ @@ -32,6 +32,7 @@ use util::{ ResultExt, }; use uuid::Uuid; +use welcome::BaseKeymap; use workspace::Pane; use workspace::{ create_and_open_local_file, notifications::simple_message_notification::MessageNotification, @@ -399,8 +400,7 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { }); workspace.focus_handle(cx).focus(cx); - //todo!() - // load_default_keymap(cx); + load_default_keymap(cx); }) .detach(); } @@ -558,38 +558,58 @@ pub fn handle_keymap_file_changes( mut user_keymap_file_rx: mpsc::UnboundedReceiver, cx: &mut AppContext, ) { + BaseKeymap::register(cx); + + let (base_keymap_tx, mut base_keymap_rx) = mpsc::unbounded(); + let mut old_base_keymap = *BaseKeymap::get_global(cx); + cx.observe_global::(move |cx| { + let new_base_keymap = *BaseKeymap::get_global(cx); + if new_base_keymap != old_base_keymap { + old_base_keymap = new_base_keymap.clone(); + base_keymap_tx.unbounded_send(()).unwrap(); + } + }) + .detach(); + cx.spawn(move |cx| async move { - // let mut settings_subscription = None; - while let Some(user_keymap_content) = user_keymap_file_rx.next().await { - if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() { - cx.update(|cx| reload_keymaps(cx, &keymap_content)).ok(); - - // todo!() - // let mut old_base_keymap = cx.read(|cx| *settings::get::(cx)); - // drop(settings_subscription); - // settings_subscription = Some(cx.update(|cx| { - // cx.observe_global::(move |cx| { - // let new_base_keymap = *settings::get::(cx); - // if new_base_keymap != old_base_keymap { - // old_base_keymap = new_base_keymap.clone(); - // reload_keymaps(cx, &keymap_content); - // } - // }) - // })); + let mut user_keymap = KeymapFile::default(); + loop { + select_biased! { + _ = base_keymap_rx.next() => {} + user_keymap_content = user_keymap_file_rx.next() => { + if let Some(user_keymap_content) = user_keymap_content { + if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() { + user_keymap = keymap_content; + } else { + continue + } + } + } } + + cx.update(|cx| reload_keymaps(cx, &user_keymap)).ok(); } }) .detach(); } fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) { - // todo!() - // cx.clear_bindings(); + cx.clear_key_bindings(); load_default_keymap(cx); keymap_content.clone().add_to_cx(cx).log_err(); cx.set_menus(app_menus()); } +pub fn load_default_keymap(cx: &mut AppContext) { + for path in ["keymaps/default.json", "keymaps/vim.json"] { + KeymapFile::load_asset(path, cx).unwrap(); + } + + if let Some(asset_path) = BaseKeymap::get_global(cx).asset_path() { + KeymapFile::load_asset(asset_path, cx).unwrap(); + } +} + fn open_local_settings_file( workspace: &mut Workspace, _: &OpenLocalSettings, From 0c4e2ef4195fe57b4b847490d3ac579037c9a0a7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jan 2024 14:08:18 -0800 Subject: [PATCH 34/67] Fix terminal selection when cursor leaves terminal bounds --- crates/terminal_view/src/terminal_element.rs | 53 +++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 328a6a1c4e8cd37dcc698ada592a60e7c9767628..ffdca7d8135d2702a3cbc9b4d42070e8ec4e41a4 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -2,10 +2,11 @@ use editor::{Cursor, HighlightedRange, HighlightedRangeLine}; use gpui::{ div, fill, point, px, red, relative, AnyElement, AsyncWindowContext, AvailableSpace, BorrowWindow, Bounds, DispatchPhase, Element, ElementId, ExternalPaths, FocusHandle, Font, - FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveElement, InteractiveElementState, - Interactivity, IntoElement, LayoutId, Model, ModelContext, ModifiersChangedEvent, MouseButton, - Pixels, PlatformInputHandler, Point, ShapedLine, StatefulInteractiveElement, StyleRefinement, - Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, WhiteSpace, WindowContext, + FontStyle, FontWeight, HighlightStyle, Hsla, InteractiveBounds, InteractiveElement, + InteractiveElementState, Interactivity, IntoElement, LayoutId, Model, ModelContext, + ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, PlatformInputHandler, Point, + ShapedLine, StatefulInteractiveElement, StyleRefinement, Styled, TextRun, TextStyle, + TextSystem, UnderlineStyle, WhiteSpace, WindowContext, }; use itertools::Itertools; use language::CursorShape; @@ -598,33 +599,48 @@ impl TerminalElement { ) { let focus = self.focus.clone(); let terminal = self.terminal.clone(); + let interactive_bounds = InteractiveBounds { + bounds: bounds.intersect(&cx.content_mask().bounds), + stacking_order: cx.stacking_order().clone(), + }; self.interactivity.on_mouse_down(MouseButton::Left, { let terminal = terminal.clone(); let focus = focus.clone(); move |e, cx| { cx.focus(&focus); - //todo!(context menu) - // v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); terminal.update(cx, |terminal, cx| { terminal.mouse_down(&e, origin); - cx.notify(); }) } }); - self.interactivity.on_mouse_move({ - let terminal = terminal.clone(); - let focus = focus.clone(); - move |e, cx| { - if e.pressed_button.is_some() && focus.is_focused(cx) && !cx.has_active_drag() { + + cx.on_mouse_event({ + let bounds = bounds.clone(); + let focus = self.focus.clone(); + let terminal = self.terminal.clone(); + move |e: &MouseMoveEvent, phase, cx| { + if phase != DispatchPhase::Bubble || !focus.is_focused(cx) { + return; + } + + if e.pressed_button.is_some() && !cx.has_active_drag() { terminal.update(cx, |terminal, cx| { terminal.mouse_drag(e, origin, bounds); cx.notify(); }) } + + if interactive_bounds.visibly_contains(&e.position, cx) { + terminal.update(cx, |terminal, cx| { + terminal.mouse_move(&e, origin); + cx.notify(); + }) + } } }); + self.interactivity.on_mouse_up( MouseButton::Left, TerminalElement::generic_button_handler( @@ -651,19 +667,6 @@ impl TerminalElement { } } }); - - self.interactivity.on_mouse_move({ - let terminal = terminal.clone(); - let focus = focus.clone(); - move |e, cx| { - if focus.is_focused(cx) { - terminal.update(cx, |terminal, cx| { - terminal.mouse_move(&e, origin); - cx.notify(); - }) - } - } - }); self.interactivity.on_scroll_wheel({ let terminal = terminal.clone(); move |e, cx| { From 32cd4d778a43c1b697da4a9f6d796a606a6ae169 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 4 Jan 2024 17:10:08 -0500 Subject: [PATCH 35/67] Render an empty placeholder when not showing file icons in the project panel (#3897) This PR makes it so when we're not showing file icons in the project panel we render an empty placeholder instead of nothing. This prevents the indentation of the items in the file tree from changing based on the presence of the icon. Release Notes: - Fixed layout shift when `project_panel.file_icons` is set to `false`. --- crates/project_panel/src/project_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index e0bee14df27479021634d3400b13850d44fa51a9..6662014c465909d782c91f45e0cb81b9c7e7053d 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1395,7 +1395,7 @@ impl ProjectPanel { .child(if let Some(icon) = &icon { div().child(IconElement::from_path(icon.to_string()).color(Color::Muted)) } else { - div() + div().size(IconSize::default().rems()).invisible() }) .child( if let (Some(editor), true) = (Some(&self.filename_editor), show_editor) { From 47476faef1e0121f66791f8e0aad906e1f263273 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 4 Jan 2024 17:25:11 -0500 Subject: [PATCH 36/67] Fix label color for inactive tabs (#3899) This PR fixes an issue where certain tabs were not using the correct color for their labels when they were inactive. Release Notes: - Fixed an issue where some inactive tabs were not using the correct label color. --- crates/diagnostics/src/diagnostics.rs | 9 +++++++-- crates/language_tools/src/lsp_log.rs | 12 +++++++++--- crates/language_tools/src/syntax_tree_view.rs | 10 ++++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 77e6a7673ff838f3f4a5ac3029b9d5bddacbb9ee..9d5a62cff19eea509aaa05c716a9188bd90a43c9 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -641,8 +641,13 @@ impl Item for ProjectDiagnosticsEditor { fn tab_content(&self, _detail: Option, selected: bool, _: &WindowContext) -> AnyElement { if self.summary.error_count == 0 && self.summary.warning_count == 0 { - let label = Label::new("No problems"); - label.into_any_element() + Label::new("No problems") + .color(if selected { + Color::Default + } else { + Color::Muted + }) + .into_any_element() } else { h_stack() .gap_1() diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index e38de7d3734a9e95cb301c4139dc6b998f033bbd..123149eae2e184df4e1b2871ea8c25125eda711f 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -10,7 +10,7 @@ use language::{LanguageServerId, LanguageServerName}; use lsp::IoKind; use project::{search::SearchQuery, Project}; use std::{borrow::Cow, sync::Arc}; -use ui::{h_stack, popover_menu, Button, Checkbox, Clickable, ContextMenu, Label, Selection}; +use ui::{popover_menu, prelude::*, Button, Checkbox, ContextMenu, Label, Selection}; use workspace::{ item::{Item, ItemHandle}, searchable::{SearchEvent, SearchableItem, SearchableItemHandle}, @@ -614,8 +614,14 @@ impl Item for LspLogView { Editor::to_item_events(event, f) } - fn tab_content(&self, _: Option, _: bool, _: &WindowContext<'_>) -> AnyElement { - Label::new("LSP Logs").into_any_element() + fn tab_content(&self, _: Option, selected: bool, _: &WindowContext<'_>) -> AnyElement { + Label::new("LSP Logs") + .color(if selected { + Color::Default + } else { + Color::Muted + }) + .into_any_element() } fn as_searchable(&self, handle: &View) -> Option> { diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index a36264261e8840a20ccb89c4ca7ca28fcd1b3851..c30564e9bfe3f8c0e0fd1f5c2f6827a4f070fd81 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -405,8 +405,14 @@ impl Item for SyntaxTreeView { fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {} - fn tab_content(&self, _: Option, _: bool, _: &WindowContext<'_>) -> AnyElement { - Label::new("Syntax Tree").into_any_element() + fn tab_content(&self, _: Option, selected: bool, _: &WindowContext<'_>) -> AnyElement { + Label::new("Syntax Tree") + .color(if selected { + Color::Default + } else { + Color::Muted + }) + .into_any_element() } fn clone_on_split( From a8efb41e56dc7ae412e948aa3599cbe4bc9f97f2 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 4 Jan 2024 17:28:07 -0500 Subject: [PATCH 37/67] Remove outdated TODOs --- crates/zed/src/app_menus.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/zed/src/app_menus.rs b/crates/zed/src/app_menus.rs index a4b0e21d6e20ee93b7d995b7f04ad58df279b316..2aff05d884265aac2538973bf9d7b65ed3d54385 100644 --- a/crates/zed/src/app_menus.rs +++ b/crates/zed/src/app_menus.rs @@ -150,14 +150,6 @@ pub fn app_menus() -> Vec> { MenuItem::action("View Dependency Licenses", crate::OpenLicenses), MenuItem::action("Show Welcome", workspace::Welcome), MenuItem::separator(), - // todo!(): Needs `feedback` crate. - // MenuItem::action("Give us feedback", feedback::feedback_editor::GiveFeedback), - // MenuItem::action( - // "Copy System Specs Into Clipboard", - // feedback::CopySystemSpecsIntoClipboard, - // ), - // MenuItem::action("File Bug Report", feedback::FileBugReport), - // MenuItem::action("Request Feature", feedback::RequestFeature), MenuItem::separator(), MenuItem::action( "Documentation", From 269b6656189be1262fcbe0413c524914e395cc39 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Thu, 4 Jan 2024 17:32:00 -0500 Subject: [PATCH 38/67] Remove outdated TODO --- crates/terminal_view/src/terminal_panel.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 9e193e83b7d54cad1d7ca0d6a7cbc5ab857a95a8..526cdf391ae331aad67088cd2462cb1b704e3889 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -410,11 +410,6 @@ impl Panel for TerminalPanel { "TerminalPanel" } - // todo!() - // fn icon_tooltip(&self) -> (String, Option>) { - // ("Terminal Panel".into(), Some(Box::new(ToggleFocus))) - // } - fn icon(&self, _cx: &WindowContext) -> Option { Some(Icon::Terminal) } From e5af1cb9a26ba4cb426074bcca7e907d38be76be Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 4 Jan 2024 17:35:18 -0500 Subject: [PATCH 39/67] Remove unneeded branches --- .../ui/src/components/button/button_like.rs | 48 +++---------------- 1 file changed, 7 insertions(+), 41 deletions(-) diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index 47aae89dfd983df876bcf7bfe4d86c656ed73f18..c3d871fe154a3b3d510f23b9d7d184a88793ebd8 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -46,7 +46,7 @@ pub enum TintColor { } impl TintColor { - fn button_style(self, cx: &mut WindowContext) -> ButtonLikeStyles { + fn button_like_style(self, cx: &mut WindowContext) -> ButtonLikeStyles { match self { TintColor::Accent => ButtonLikeStyles { background: cx.theme().status().info_background, @@ -54,6 +54,7 @@ impl TintColor { label_color: cx.theme().colors().text, icon_color: cx.theme().colors().text, }, + // TODO: Finish tint colors. _ => ButtonLikeStyles { background: gpui::red(), border_color: gpui::red(), @@ -112,14 +113,7 @@ impl ButtonStyle { label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle::Tinted(TintColor::Accent) => TintColor::Accent.button_style(cx), - // TODO: Finish tint colors - ButtonStyle::Tinted(_) => ButtonLikeStyles { - background: gpui::red(), - border_color: gpui::red(), - label_color: gpui::red(), - icon_color: gpui::red(), - }, + ButtonStyle::Tinted(tint) => tint.button_like_style(cx), ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_background, border_color: transparent_black(), @@ -143,14 +137,7 @@ impl ButtonStyle { label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle::Tinted(TintColor::Accent) => TintColor::Accent.button_style(cx), - // TODO: Finish tint colors - ButtonStyle::Tinted(_) => ButtonLikeStyles { - background: gpui::red(), - border_color: gpui::red(), - label_color: gpui::red(), - icon_color: gpui::red(), - }, + ButtonStyle::Tinted(tint) => tint.button_like_style(cx), ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_hover, border_color: transparent_black(), @@ -176,14 +163,7 @@ impl ButtonStyle { label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle::Tinted(TintColor::Accent) => TintColor::Accent.button_style(cx), - // TODO: Finish tint colors - ButtonStyle::Tinted(_) => ButtonLikeStyles { - background: gpui::red(), - border_color: gpui::red(), - label_color: gpui::red(), - icon_color: gpui::red(), - }, + ButtonStyle::Tinted(tint) => tint.button_like_style(cx), ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_active, border_color: transparent_black(), @@ -210,14 +190,7 @@ impl ButtonStyle { label_color: Color::Default.color(cx), icon_color: Color::Default.color(cx), }, - ButtonStyle::Tinted(TintColor::Accent) => TintColor::Accent.button_style(cx), - // TODO: Finish tint colors - ButtonStyle::Tinted(_) => ButtonLikeStyles { - background: gpui::red(), - border_color: gpui::red(), - label_color: gpui::red(), - icon_color: gpui::red(), - }, + ButtonStyle::Tinted(tint) => tint.button_like_style(cx), ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_background, border_color: cx.theme().colors().border_focused, @@ -242,14 +215,7 @@ impl ButtonStyle { label_color: Color::Disabled.color(cx), icon_color: Color::Disabled.color(cx), }, - ButtonStyle::Tinted(TintColor::Accent) => TintColor::Accent.button_style(cx), - // TODO: Finish tint colors - ButtonStyle::Tinted(_) => ButtonLikeStyles { - background: gpui::red(), - border_color: gpui::red(), - label_color: gpui::red(), - icon_color: gpui::red(), - }, + ButtonStyle::Tinted(tint) => tint.button_like_style(cx), ButtonStyle::Subtle => ButtonLikeStyles { background: cx.theme().colors().ghost_element_disabled, border_color: cx.theme().colors().border_disabled, From 61db60b3e21a9a562d22afc222bdb152b9767286 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jan 2024 14:55:45 -0800 Subject: [PATCH 40/67] Fix incorrect placement of terminal selection when dragging Co-authored-by: Mikayla --- crates/terminal/src/mappings/mouse.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index a32d83d28ddb99416cff22a85322184acabb2ac9..19c699216a77d47ce173cff06d7258f6fafa1649 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -162,22 +162,20 @@ pub fn mouse_side( pos: Point, cur_size: TerminalSize, ) -> alacritty_terminal::index::Direction { - let cell_width = cur_size.cell_width.floor(); + let cell_width = cur_size.cell_width; if cell_width == px(0.) { return Side::Right; } - let x = pos.x.floor(); - - let cell_x = cmp::max(px(0.), x - cell_width) % cell_width; - let half_cell_width = (cur_size.cell_width / 2.0).floor(); + let cell_x = cmp::max(px(0.), pos.x) % cell_width; + let half_cell_width = cur_size.cell_width / 2.0; let additional_padding = (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width; let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding; //Width: Pixels or columns? if cell_x > half_cell_width // Edge case when mouse leaves the window. - || x >= end_of_grid + || pos.x >= end_of_grid { Side::Right } else { From ba13540c77bb8a1e755b304150ea9d5d590b737d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jan 2024 15:05:39 -0800 Subject: [PATCH 41/67] Fix inconsistent selection start when dragging outside of terminal bounds Co-authored-by: Mikayla --- crates/terminal/src/mappings/mouse.rs | 50 +++++++++++++++------------ crates/terminal/src/terminal.rs | 15 +++----- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index 19c699216a77d47ce173cff06d7258f6fafa1649..af3e6b640fd64fe6dc3e2e2364a08107bf3634e4 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -158,37 +158,41 @@ pub fn mouse_moved_report(point: AlacPoint, e: &MouseMoveEvent, mode: TermMode) } } -pub fn mouse_side( +pub fn grid_point(pos: Point, cur_size: TerminalSize, display_offset: usize) -> AlacPoint { + grid_point_and_side(pos, cur_size, display_offset).0 +} + +pub fn grid_point_and_side( pos: Point, cur_size: TerminalSize, -) -> alacritty_terminal::index::Direction { - let cell_width = cur_size.cell_width; - if cell_width == px(0.) { - return Side::Right; - } - - let cell_x = cmp::max(px(0.), pos.x) % cell_width; + display_offset: usize, +) -> (AlacPoint, Side) { + let mut col = GridCol((pos.x / cur_size.cell_width) as usize); + let cell_x = cmp::max(px(0.), pos.x) % cur_size.cell_width; let half_cell_width = cur_size.cell_width / 2.0; - let additional_padding = (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width; - let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding; - - //Width: Pixels or columns? - if cell_x > half_cell_width - // Edge case when mouse leaves the window. - || pos.x >= end_of_grid - { + let mut side = if cell_x > half_cell_width { Side::Right } else { Side::Left - } -} + }; -pub fn grid_point(pos: Point, cur_size: TerminalSize, display_offset: usize) -> AlacPoint { - let col = GridCol((pos.x / cur_size.cell_width) as usize); + if col > cur_size.last_column() { + col = cur_size.last_column(); + side = Side::Right; + } let col = min(col, cur_size.last_column()); - let line = (pos.y / cur_size.line_height) as i32; - let line = min(line, cur_size.bottommost_line().0); - AlacPoint::new(GridLine(line - display_offset as i32), col) + let mut line = (pos.y / cur_size.line_height) as i32; + if line > cur_size.bottommost_line() { + line = cur_size.bottommost_line().0 as i32; + side = Side::Right; + } else if line < 0 { + side = Side::Left; + } + + ( + AlacPoint::new(GridLine(line - display_offset as i32), col), + side, + ) } ///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index b15bd7c6d659cc7794b770934e95775c0ae41507..70fceae66a5b8be562513843e03a1675ff66f3be 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -28,7 +28,8 @@ use futures::{ }; use mappings::mouse::{ - alt_scroll, grid_point, mouse_button_report, mouse_moved_report, mouse_side, scroll_report, + alt_scroll, grid_point, grid_point_and_side, mouse_button_report, mouse_moved_report, + scroll_report, }; use procinfo::LocalProcessInfo; @@ -704,14 +705,12 @@ impl Terminal { } InternalEvent::UpdateSelection(position) => { if let Some(mut selection) = term.selection.take() { - let point = grid_point( + let (point, side) = grid_point_and_side( *position, self.last_content.size, term.grid().display_offset(), ); - let side = mouse_side(*position, self.last_content.size); - selection.update(point, side); term.selection = Some(selection); @@ -1088,12 +1087,11 @@ impl Terminal { let position = e.position - origin; self.last_mouse_position = Some(position); if self.mouse_mode(e.modifiers.shift) { - let point = grid_point( + let (point, side) = grid_point_and_side( position, self.last_content.size, self.last_content.display_offset, ); - let side = mouse_side(position, self.last_content.size); if self.mouse_changed(point, side) { if let Some(bytes) = mouse_moved_report(point, e, self.last_content.mode) { @@ -1175,15 +1173,12 @@ impl Terminal { } } else if e.button == MouseButton::Left { let position = e.position - origin; - let point = grid_point( + let (point, side) = grid_point_and_side( position, self.last_content.size, self.last_content.display_offset, ); - // Use .opposite so that selection is inclusive of the cell clicked. - let side = mouse_side(position, self.last_content.size); - let selection_type = match e.click_count { 0 => return, //This is a release 1 => Some(SelectionType::Simple), From 3f06a050608f63f2c93aca1d90c96e7385d3b27e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 4 Jan 2024 18:08:28 -0500 Subject: [PATCH 42/67] Iterate on collab panel filter input style (#3900) This PR takes another pass at the collab panel filter input to improve its styling. Release Notes: - Improved the look of the filter input in the collab panel. --- crates/collab_ui/src/collab_panel.rs | 60 ++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 3ed07e5bd1b8b3985f58d8ca89dd04e676f5425a..f4856b22017cc2bcd5cfc76cfb1dedb291fe2fe2 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -11,15 +11,16 @@ use channel::{Channel, ChannelEvent, ChannelId, ChannelStore}; use client::{Client, Contact, User, UserStore}; use contact_finder::ContactFinder; use db::kvp::KEY_VALUE_STORE; -use editor::Editor; +use editor::{Editor, EditorElement, EditorStyle}; use feature_flags::{ChannelsAlpha, FeatureFlagAppExt, FeatureFlagViewExt}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ actions, canvas, div, fill, list, overlay, point, prelude::*, px, serde_json, AnyElement, AppContext, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent, Div, EventEmitter, - FocusHandle, FocusableView, InteractiveElement, IntoElement, ListOffset, ListState, Model, - MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, SharedString, - Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, + FocusHandle, FocusableView, FontStyle, FontWeight, InteractiveElement, IntoElement, ListOffset, + ListState, Model, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, + RenderOnce, SharedString, Styled, Subscription, Task, TextStyle, View, ViewContext, + VisualContext, WeakView, WhiteSpace, }; use menu::{Cancel, Confirm, SelectNext, SelectPrev}; use project::{Fs, Project}; @@ -29,10 +30,9 @@ use settings::{Settings, SettingsStore}; use smallvec::SmallVec; use std::{mem, sync::Arc}; use theme::{ActiveTheme, ThemeSettings}; -use ui::prelude::*; use ui::{ - h_stack, v_stack, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize, - Label, ListHeader, ListItem, Tooltip, + prelude::*, Avatar, Button, Color, ContextMenu, Icon, IconButton, IconElement, IconSize, Label, + ListHeader, ListItem, Tooltip, }; use util::{maybe, ResultExt, TryFutureExt}; use workspace::{ @@ -1749,15 +1749,49 @@ impl CollabPanel { .size_full() .child(list(self.list_state.clone()).full()) .child( - v_stack().p_2().child( - v_stack() - .border_primary(cx) - .border_t() - .child(self.filter_editor.clone()), - ), + v_stack() + .child(div().mx_2().border_primary(cx).border_t()) + .child( + v_stack() + .p_2() + .child(self.render_filter_input(&self.filter_editor, cx)), + ), ) } + fn render_filter_input( + &self, + editor: &View, + cx: &mut ViewContext, + ) -> impl IntoElement { + let settings = ThemeSettings::get_global(cx); + let text_style = TextStyle { + color: if editor.read(cx).read_only() { + cx.theme().colors().text_disabled + } else { + cx.theme().colors().text + }, + font_family: settings.ui_font.family.clone(), + font_features: settings.ui_font.features, + font_size: rems(0.875).into(), + font_weight: FontWeight::NORMAL, + font_style: FontStyle::Normal, + line_height: relative(1.3).into(), + background_color: None, + underline: None, + white_space: WhiteSpace::Normal, + }; + + EditorElement::new( + editor, + EditorStyle { + local_player: cx.theme().players().local(), + text: text_style, + ..Default::default() + }, + ) + } + fn render_header( &self, section: Section, From b3d8b2313901b29402cb145578b4fce2b22cb435 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 12:17:38 +0100 Subject: [PATCH 43/67] terminal/search: Partially fix search in terminal. There are two issues with search in terminal as is: - terminal's pane is not registered as a "legit" pane, so we dispatch buffer search bar::Deploy on the most recent "legit" pane. By legit I mean that workspace::active_pane will *never* return terminal pane as active. - We've had the implementation of as_searchable commented out. Duh! This commit fixes second issue. That means that if you drag the terminal over to the main editor pane (so that it's in a "legit" pane), it'll work. 1st issue still stands though. --- Cargo.lock | 1 + crates/terminal_view/Cargo.toml | 2 +- crates/terminal_view/src/terminal_panel.rs | 6 +++--- crates/terminal_view/src/terminal_view.rs | 9 ++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13d4be62337ae0557fbc59538db4a4bdceacd22e..4371742056075385f342917ae078a0b48698506f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7762,6 +7762,7 @@ dependencies = [ "procinfo", "project", "rand 0.8.5", + "search", "serde", "serde_derive", "settings", diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index 110199a8d4b979734fda44c6d15ce03b583ed16f..d84846018346dc530465eb66835f9a9cc894ac59 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -13,7 +13,7 @@ editor = { path = "../editor" } language = { path = "../language" } gpui = { path = "../gpui" } project = { path = "../project" } -# search = { path = "../search" } +search = { path = "../search" } settings = { path = "../settings" } theme = { path = "../theme" } util = { path = "../util" } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 526cdf391ae331aad67088cd2462cb1b704e3889..f5179d39a971dcc2ebab3191dec9075806b5bc2a 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -101,9 +101,9 @@ impl TerminalPanel { }) .into_any_element() }); - // let buffer_search_bar = cx.build_view(search::BufferSearchBar::new); - // pane.toolbar() - // .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx)); + let buffer_search_bar = cx.add_view(search::BufferSearchBar::new); + pane.toolbar() + .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx)); pane }); let subscriptions = vec![ diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index d4dea29b49e71e66604ec56799d166778792ae9a..521efef7906c621e4ffabc9a6060aae50fd2e8dd 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -28,7 +28,7 @@ use workspace::{ item::{BreadcrumbText, Item, ItemEvent}, notifications::NotifyResultExt, register_deserializable_item, - searchable::{SearchEvent, SearchOptions, SearchableItem}, + searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle}, CloseActiveItem, NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId, }; @@ -714,10 +714,9 @@ impl Item for TerminalView { false } - // todo!(search) - // fn as_searchable(&self, handle: &View) -> Option> { - // Some(Box::new(handle.clone())) - // } + fn as_searchable(&self, handle: &View) -> Option> { + Some(Box::new(handle.clone())) + } fn breadcrumb_location(&self) -> ToolbarItemLocation { ToolbarItemLocation::PrimaryLeft From cd0b15e23d9b68249a6dc544c01fff101e140456 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 12:18:48 +0100 Subject: [PATCH 44/67] fixup! terminal/search: Partially fix search in terminal. There are two issues with search in terminal as is: - terminal's pane is not registered as a "legit" pane, so we dispatch buffer search bar::Deploy on the most recent "legit" pane. By legit I mean that workspace::active_pane will *never* return terminal pane as active. - We've had the implementation of as_searchable commented out. Duh! --- crates/terminal_view/src/terminal_panel.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index f5179d39a971dcc2ebab3191dec9075806b5bc2a..7118d6f767b37ea9a21f73e51a88407c8a4c8c65 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -101,7 +101,7 @@ impl TerminalPanel { }) .into_any_element() }); - let buffer_search_bar = cx.add_view(search::BufferSearchBar::new); + let buffer_search_bar = cx.new_view(search::BufferSearchBar::new); pane.toolbar() .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx)); pane From f70eddc988dfcfa0c83db6d2a16ca80b820ae68e Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 14:56:35 +0100 Subject: [PATCH 45/67] Explore registrar-based API for search bar. This commit adds a Registrar trait for use by search crate. Registrar can register actions on some target and search can utilize that trait to opaquely add actions on that target. Notably, search is now opt-in (it always was in zed2 actually). Having editor doesn't make it searchable straight out of the gate. You might have to call BufferSearchBar::new a bunch more. --- crates/search/src/buffer_search.rs | 154 ++++++++++++--------- crates/terminal_view/src/terminal_panel.rs | 40 +++++- 2 files changed, 125 insertions(+), 69 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 67aa4955bc692483faafb9b6291e9e8550dac859..3521b1e8490d88c21ec66323ce9f03825ea6677f 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -422,89 +422,115 @@ impl ToolbarItemView for BufferSearchBar { } } +/// Registrar inverts the dependency between search and it's downstream user, allowing said downstream user to register search action without knowing exactly what those actions are. +pub trait SearchActionsRegistrar { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ); +} + impl BufferSearchBar { - fn register(workspace: &mut Workspace) { - workspace.register_action(move |workspace, deploy: &Deploy, cx| { - let pane = workspace.active_pane(); - - pane.update(cx, |this, cx| { - this.toolbar().update(cx, |this, cx| { - if let Some(search_bar) = this.item_of_type::() { - search_bar.update(cx, |this, cx| { - this.deploy(deploy, cx); - }); - return; - } - let view = cx.new_view(|cx| BufferSearchBar::new(cx)); - this.add_item(view.clone(), cx); - view.update(cx, |this, cx| this.deploy(deploy, cx)); - cx.notify(); - }) + pub fn register_inner( + registrar: &mut impl SearchActionsRegistrar, + supported_options: &workspace::searchable::SearchOptions, + ) { + // supported_options controls whether the action is registered in the first place, + // but we still perform dynamic checks as e.g. if a view (like Workspace) uses SearchableItemHandle, they cannot know in advance + // whether a given option is supported. + if supported_options.case { + registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { + if this.supported_options().case { + this.toggle_case_sensitive(action, cx); + } }); - }); - fn register_action( - workspace: &mut Workspace, - update: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - workspace.register_action(move |workspace, action: &A, cx| { - let pane = workspace.active_pane(); - pane.update(cx, move |this, cx| { - this.toolbar().update(cx, move |this, cx| { - if let Some(search_bar) = this.item_of_type::() { - search_bar.update(cx, move |this, cx| update(this, action, cx)); - cx.notify(); - } - }) - }); + } + if supported_options.word { + registrar.register_handler(|this, action: &ToggleWholeWord, cx| { + if this.supported_options().word { + this.toggle_whole_word(action, cx); + } }); } - register_action(workspace, |this, action: &ToggleCaseSensitive, cx| { - if this.supported_options().case { - this.toggle_case_sensitive(action, cx); - } - }); - register_action(workspace, |this, action: &ToggleWholeWord, cx| { - if this.supported_options().word { - this.toggle_whole_word(action, cx); - } - }); - register_action(workspace, |this, action: &ToggleReplace, cx| { - if this.supported_options().replacement { - this.toggle_replace(action, cx); - } - }); - register_action(workspace, |this, _: &ActivateRegexMode, cx| { - if this.supported_options().regex { - this.activate_search_mode(SearchMode::Regex, cx); - } - }); - register_action(workspace, |this, _: &ActivateTextMode, cx| { + if supported_options.replacement { + registrar.register_handler(|this, action: &ToggleReplace, cx| { + if this.supported_options().replacement { + this.toggle_replace(action, cx); + } + }); + } + + if supported_options.regex { + registrar.register_handler(|this, _: &ActivateRegexMode, cx| { + if this.supported_options().regex { + this.activate_search_mode(SearchMode::Regex, cx); + } + }); + } + + registrar.register_handler(|this, _: &ActivateTextMode, cx| { this.activate_search_mode(SearchMode::Text, cx); }); - register_action(workspace, |this, action: &CycleMode, cx| { - if this.supported_options().regex { - // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting - // cycling. - this.cycle_mode(action, cx) - } - }); - register_action(workspace, |this, action: &SelectNextMatch, cx| { + + if supported_options.regex { + registrar.register_handler(|this, action: &CycleMode, cx| { + if this.supported_options().regex { + // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting + // cycling. + this.cycle_mode(action, cx) + } + }); + } + + registrar.register_handler(|this, action: &SelectNextMatch, cx| { this.select_next_match(action, cx); }); - register_action(workspace, |this, action: &SelectPrevMatch, cx| { + registrar.register_handler(|this, action: &SelectPrevMatch, cx| { this.select_prev_match(action, cx); }); - register_action(workspace, |this, action: &SelectAllMatches, cx| { + registrar.register_handler(|this, action: &SelectAllMatches, cx| { this.select_all_matches(action, cx); }); - register_action(workspace, |this, _: &editor::Cancel, cx| { + registrar.register_handler(|this, _: &editor::Cancel, cx| { if !this.dismissed { this.dismiss(&Dismiss, cx); return; } cx.propagate(); }); + registrar.register_handler(|this, deploy, cx| { + this.deploy(deploy, cx); + }) + } + fn register(workspace: &mut Workspace) { + impl SearchActionsRegistrar for Workspace { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + self.register_action(move |workspace, action: &A, cx| { + let pane = workspace.active_pane(); + pane.update(cx, move |this, cx| { + this.toolbar().update(cx, move |this, cx| { + if let Some(search_bar) = this.item_of_type::() { + search_bar.update(cx, move |this, cx| callback(this, action, cx)); + cx.notify(); + } + }) + }); + }); + } + } + Self::register_inner( + workspace, + &workspace::searchable::SearchOptions { + case: true, + word: true, + regex: true, + replacement: true, + }, + ); } pub fn new(cx: &mut ViewContext) -> Self { let query_editor = cx.new_view(|cx| Editor::single_line(cx)); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 7118d6f767b37ea9a21f73e51a88407c8a4c8c65..f363c5228bc7ad0056b2ece3321a60b34305b55d 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -3,11 +3,13 @@ use std::{path::PathBuf, sync::Arc}; use crate::TerminalView; use db::kvp::KEY_VALUE_STORE; use gpui::{ - actions, div, serde_json, AppContext, AsyncWindowContext, Entity, EventEmitter, ExternalPaths, - FocusHandle, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, - Task, View, ViewContext, VisualContext, WeakView, WindowContext, + actions, div, serde_json, AppContext, AsyncWindowContext, Div, Entity, EventEmitter, + ExternalPaths, FocusHandle, FocusableView, InteractiveElement, IntoElement, ParentElement, + Pixels, Render, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, + WindowContext, }; use project::Fs; +use search::{buffer_search::SearchActionsRegistrar, BufferSearchBar}; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings}; @@ -17,6 +19,7 @@ use workspace::{ dock::{DockPosition, Panel, PanelEvent}, item::Item, pane, + searchable::SearchableItem, ui::Icon, Pane, Workspace, }; @@ -328,9 +331,36 @@ impl TerminalPanel { impl EventEmitter for TerminalPanel {} +struct ActionsRegistrar<'a, 'b> +where + 'b: 'a, +{ + div: Option
, + cx: &'a mut ViewContext<'b, TerminalPanel>, +} +impl SearchActionsRegistrar for ActionsRegistrar<'_, '_> { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + self.div = self.div.take().map(|div| { + div.on_action(self.cx.listener(move |this, action, cx| { + this.pane + .read(cx) + .toolbar() + .read(cx) + .item_of_type::() + .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx))); + })) + }); + } +} impl Render for TerminalPanel { - fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - div().size_full().child(self.pane.clone()) + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let div = div(); + let mut registrar = ActionsRegistrar { div: Some(div), cx }; + BufferSearchBar::register_inner(&mut registrar, &TerminalView::supported_options()); + registrar.div.unwrap().size_full().child(self.pane.clone()) } } From 5ad125a9e93dd8223b4ca10823cf41cdf6b9078b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 15:51:56 +0100 Subject: [PATCH 46/67] Touchups to registrar API --- Cargo.lock | 1 - crates/feedback/Cargo.toml | 1 - crates/search/src/buffer_search.rs | 84 ++++++++-------------- crates/terminal_view/src/terminal_panel.rs | 3 +- 4 files changed, 32 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4371742056075385f342917ae078a0b48698506f..0316c343cb4ea5922897d9fc8ab417793e770ade 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2486,7 +2486,6 @@ dependencies = [ "postage", "project", "regex", - "search", "serde", "serde_derive", "settings", diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index c6c8b9f4b24c80793ca1d177d0fe9eb0682bbf69..e14df6a2d48264b37686a5bb942cc9ce9dcb7504 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -18,7 +18,6 @@ gpui = { path = "../gpui" } language = { path = "../language" } menu = { path = "../menu" } project = { path = "../project" } -search = { path = "../search" } settings = { path = "../settings" } theme = { path = "../theme" } ui = { path = "../ui" } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 3521b1e8490d88c21ec66323ce9f03825ea6677f..afd0f6c615649a2ef61f997e0edd6a330ffea5a2 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -431,57 +431,42 @@ pub trait SearchActionsRegistrar { } impl BufferSearchBar { - pub fn register_inner( - registrar: &mut impl SearchActionsRegistrar, - supported_options: &workspace::searchable::SearchOptions, - ) { - // supported_options controls whether the action is registered in the first place, - // but we still perform dynamic checks as e.g. if a view (like Workspace) uses SearchableItemHandle, they cannot know in advance - // whether a given option is supported. - if supported_options.case { - registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { - if this.supported_options().case { - this.toggle_case_sensitive(action, cx); - } - }); - } - if supported_options.word { - registrar.register_handler(|this, action: &ToggleWholeWord, cx| { - if this.supported_options().word { - this.toggle_whole_word(action, cx); - } - }); - } + pub fn register_inner(registrar: &mut impl SearchActionsRegistrar) { + registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { + if this.supported_options().case { + this.toggle_case_sensitive(action, cx); + } + }); - if supported_options.replacement { - registrar.register_handler(|this, action: &ToggleReplace, cx| { - if this.supported_options().replacement { - this.toggle_replace(action, cx); - } - }); - } + registrar.register_handler(|this, action: &ToggleWholeWord, cx| { + if this.supported_options().word { + this.toggle_whole_word(action, cx); + } + }); - if supported_options.regex { - registrar.register_handler(|this, _: &ActivateRegexMode, cx| { - if this.supported_options().regex { - this.activate_search_mode(SearchMode::Regex, cx); - } - }); - } + registrar.register_handler(|this, action: &ToggleReplace, cx| { + if this.supported_options().replacement { + this.toggle_replace(action, cx); + } + }); + + registrar.register_handler(|this, _: &ActivateRegexMode, cx| { + if this.supported_options().regex { + this.activate_search_mode(SearchMode::Regex, cx); + } + }); registrar.register_handler(|this, _: &ActivateTextMode, cx| { this.activate_search_mode(SearchMode::Text, cx); }); - if supported_options.regex { - registrar.register_handler(|this, action: &CycleMode, cx| { - if this.supported_options().regex { - // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting - // cycling. - this.cycle_mode(action, cx) - } - }); - } + registrar.register_handler(|this, action: &CycleMode, cx| { + if this.supported_options().regex { + // If regex is not supported then search has just one mode (text) - in that case there's no point in supporting + // cycling. + this.cycle_mode(action, cx) + } + }); registrar.register_handler(|this, action: &SelectNextMatch, cx| { this.select_next_match(action, cx); @@ -500,6 +485,7 @@ impl BufferSearchBar { cx.propagate(); }); registrar.register_handler(|this, deploy, cx| { + dbg!("Deploying?"); this.deploy(deploy, cx); }) } @@ -522,15 +508,7 @@ impl BufferSearchBar { }); } } - Self::register_inner( - workspace, - &workspace::searchable::SearchOptions { - case: true, - word: true, - regex: true, - replacement: true, - }, - ); + Self::register_inner(workspace); } pub fn new(cx: &mut ViewContext) -> Self { let query_editor = cx.new_view(|cx| Editor::single_line(cx)); diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index f363c5228bc7ad0056b2ece3321a60b34305b55d..ed228faba8c7810657d27e6274235dfaa2ae405b 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -19,7 +19,6 @@ use workspace::{ dock::{DockPosition, Panel, PanelEvent}, item::Item, pane, - searchable::SearchableItem, ui::Icon, Pane, Workspace, }; @@ -359,7 +358,7 @@ impl Render for TerminalPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let div = div(); let mut registrar = ActionsRegistrar { div: Some(div), cx }; - BufferSearchBar::register_inner(&mut registrar, &TerminalView::supported_options()); + BufferSearchBar::register_inner(&mut registrar); registrar.div.unwrap().size_full().child(self.pane.clone()) } } From 9cdcdbea419ef5c62bcc25c876da63268ed8c4f1 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:07:18 +0100 Subject: [PATCH 47/67] Use Registrar in Assistant Panel. This fixes various actions like "Activate regex mode" that were dispatched onto main pane instead of assistant search bar. --- crates/assistant/src/assistant_panel.rs | 34 +++++++++++++++++++++++-- crates/search/src/buffer_search.rs | 1 - 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 9221d87f60fda92990997a1401bf54325a099c6b..7ab6aba227dc1cccb9957afe02144d33981ab404 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -38,7 +38,7 @@ use gpui::{ }; use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _}; use project::Project; -use search::BufferSearchBar; +use search::{buffer_search::SearchActionsRegistrar, BufferSearchBar}; use semantic_index::{SemanticIndex, SemanticIndexStatus}; use settings::{Settings, SettingsStore}; use std::{ @@ -1100,6 +1100,26 @@ fn build_api_key_editor(cx: &mut ViewContext) -> View { }) } +struct SearchRegistrar<'a, 'b> { + div: Option
, + cx: &'a mut ViewContext<'b, AssistantPanel>, +} + +impl SearchActionsRegistrar for SearchRegistrar<'_, '_> { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + self.div = self.div.take().map(|div| { + div.on_action(self.cx.listener(move |this, action, cx| { + this.toolbar + .read(cx) + .item_of_type::() + .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx))); + })) + }); + } +} impl Render for AssistantPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { if let Some(api_key_editor) = self.api_key_editor.clone() { @@ -1156,6 +1176,16 @@ impl Render for AssistantPanel { div() }); + let contents = if self.active_editor().is_some() { + let mut registrar = SearchRegistrar { + div: Some(div()), + cx, + }; + BufferSearchBar::register_inner(&mut registrar); + registrar.div.unwrap() + } else { + div() + }; v_stack() .key_context("AssistantPanel") .size_full() @@ -1176,7 +1206,7 @@ impl Render for AssistantPanel { Some(self.toolbar.clone()) }) .child( - div() + contents .flex_1() .child(if let Some(editor) = self.active_editor() { editor.clone().into_any_element() diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index afd0f6c615649a2ef61f997e0edd6a330ffea5a2..6e44db60c54d3dd6b048894d841b0bc7bdbde55a 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -485,7 +485,6 @@ impl BufferSearchBar { cx.propagate(); }); registrar.register_handler(|this, deploy, cx| { - dbg!("Deploying?"); this.deploy(deploy, cx); }) } From b6655def70599bb3d6aa8d8985b1c2e104206a26 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 17:03:22 +0100 Subject: [PATCH 48/67] Add DivRegistrar to reduce code duplication --- crates/assistant/src/assistant_panel.rs | 30 +++------------ crates/search/src/buffer_search.rs | 40 ++++++++++++++++++++ crates/terminal_view/src/terminal_panel.rs | 44 +++++++--------------- 3 files changed, 59 insertions(+), 55 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 7ab6aba227dc1cccb9957afe02144d33981ab404..371cc9007a8c1f2bc5fd013573294bcb024cc1d9 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -38,7 +38,7 @@ use gpui::{ }; use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _}; use project::Project; -use search::{buffer_search::SearchActionsRegistrar, BufferSearchBar}; +use search::{buffer_search::DivRegistrar, BufferSearchBar}; use semantic_index::{SemanticIndex, SemanticIndexStatus}; use settings::{Settings, SettingsStore}; use std::{ @@ -1100,26 +1100,6 @@ fn build_api_key_editor(cx: &mut ViewContext) -> View { }) } -struct SearchRegistrar<'a, 'b> { - div: Option
, - cx: &'a mut ViewContext<'b, AssistantPanel>, -} - -impl SearchActionsRegistrar for SearchRegistrar<'_, '_> { - fn register_handler( - &mut self, - callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - self.div = self.div.take().map(|div| { - div.on_action(self.cx.listener(move |this, action, cx| { - this.toolbar - .read(cx) - .item_of_type::() - .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx))); - })) - }); - } -} impl Render for AssistantPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { if let Some(api_key_editor) = self.api_key_editor.clone() { @@ -1177,12 +1157,12 @@ impl Render for AssistantPanel { }); let contents = if self.active_editor().is_some() { - let mut registrar = SearchRegistrar { - div: Some(div()), + let mut registrar = DivRegistrar::new( + |panel, cx| panel.toolbar.read(cx).item_of_type::(), cx, - }; + ); BufferSearchBar::register_inner(&mut registrar); - registrar.div.unwrap() + registrar.into_div() } else { div() }; diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 6e44db60c54d3dd6b048894d841b0bc7bdbde55a..371037e0478f62d0edd084470388c331ef29d586 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -430,6 +430,46 @@ pub trait SearchActionsRegistrar { ); } +type GetSearchBar = + for<'a, 'b> fn(&'a T, &'a mut ViewContext<'b, T>) -> Option>; + +/// Registers search actions on a div that can be taken out. +pub struct DivRegistrar<'a, 'b, T: 'static> { + div: Option
, + cx: &'a mut ViewContext<'b, T>, + search_getter: GetSearchBar, +} + +impl<'a, 'b, T: 'static> DivRegistrar<'a, 'b, T> { + pub fn new(search_getter: GetSearchBar, cx: &'a mut ViewContext<'b, T>) -> Self { + Self { + div: Some(div()), + cx, + search_getter, + } + } + pub fn into_div(self) -> Div { + // This option is always Some; it's an option in the first place because we want to call methods + // on div that require ownership. + self.div.unwrap() + } +} + +impl SearchActionsRegistrar for DivRegistrar<'_, '_, T> { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + let getter = self.search_getter; + self.div = self.div.take().map(|div| { + div.on_action(self.cx.listener(move |this, action, cx| { + (getter)(this, cx) + .clone() + .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx))); + })) + }); + } +} impl BufferSearchBar { pub fn register_inner(registrar: &mut impl SearchActionsRegistrar) { registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index ed228faba8c7810657d27e6274235dfaa2ae405b..82d7208ef88ba625d8430e8f5d314a9b00cb7719 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -3,13 +3,12 @@ use std::{path::PathBuf, sync::Arc}; use crate::TerminalView; use db::kvp::KEY_VALUE_STORE; use gpui::{ - actions, div, serde_json, AppContext, AsyncWindowContext, Div, Entity, EventEmitter, - ExternalPaths, FocusHandle, FocusableView, InteractiveElement, IntoElement, ParentElement, - Pixels, Render, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, - WindowContext, + actions, serde_json, AppContext, AsyncWindowContext, Entity, EventEmitter, ExternalPaths, + FocusHandle, FocusableView, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, + Task, View, ViewContext, VisualContext, WeakView, WindowContext, }; use project::Fs; -use search::{buffer_search::SearchActionsRegistrar, BufferSearchBar}; +use search::{buffer_search::DivRegistrar, BufferSearchBar}; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings}; @@ -330,36 +329,21 @@ impl TerminalPanel { impl EventEmitter for TerminalPanel {} -struct ActionsRegistrar<'a, 'b> -where - 'b: 'a, -{ - div: Option
, - cx: &'a mut ViewContext<'b, TerminalPanel>, -} -impl SearchActionsRegistrar for ActionsRegistrar<'_, '_> { - fn register_handler( - &mut self, - callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - self.div = self.div.take().map(|div| { - div.on_action(self.cx.listener(move |this, action, cx| { - this.pane +impl Render for TerminalPanel { + fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let mut registrar = DivRegistrar::new( + |panel, cx| { + panel + .pane .read(cx) .toolbar() .read(cx) .item_of_type::() - .map(|search_bar| search_bar.update(cx, |this, cx| callback(this, action, cx))); - })) - }); - } -} -impl Render for TerminalPanel { - fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let div = div(); - let mut registrar = ActionsRegistrar { div: Some(div), cx }; + }, + cx, + ); BufferSearchBar::register_inner(&mut registrar); - registrar.div.unwrap().size_full().child(self.pane.clone()) + registrar.into_div().size_full().child(self.pane.clone()) } } From 783256c80e6222acd39015fb6ca85f977dacc1b1 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 17:04:28 +0100 Subject: [PATCH 49/67] Move Registrar implementation for Workspace to outer scope. This fixes various actions like "Activate regex mode" that were dispatched onto main pane instead of assistant search bar. --- crates/search/src/buffer_search.rs | 38 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 371037e0478f62d0edd084470388c331ef29d586..7cce3c71c6c1b61ee0679be22d8e4260dc73a17d 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -470,6 +470,26 @@ impl SearchActionsRegistrar for DivRegistrar<'_, '_, T> { }); } } + +/// Register actions for an active pane. +impl SearchActionsRegistrar for Workspace { + fn register_handler( + &mut self, + callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), + ) { + self.register_action(move |workspace, action: &A, cx| { + let pane = workspace.active_pane(); + pane.update(cx, move |this, cx| { + this.toolbar().update(cx, move |this, cx| { + if let Some(search_bar) = this.item_of_type::() { + search_bar.update(cx, move |this, cx| callback(this, action, cx)); + cx.notify(); + } + }) + }); + }); + } +} impl BufferSearchBar { pub fn register_inner(registrar: &mut impl SearchActionsRegistrar) { registrar.register_handler(|this, action: &ToggleCaseSensitive, cx| { @@ -529,24 +549,6 @@ impl BufferSearchBar { }) } fn register(workspace: &mut Workspace) { - impl SearchActionsRegistrar for Workspace { - fn register_handler( - &mut self, - callback: fn(&mut BufferSearchBar, &A, &mut ViewContext), - ) { - self.register_action(move |workspace, action: &A, cx| { - let pane = workspace.active_pane(); - pane.update(cx, move |this, cx| { - this.toolbar().update(cx, move |this, cx| { - if let Some(search_bar) = this.item_of_type::() { - search_bar.update(cx, move |this, cx| callback(this, action, cx)); - cx.notify(); - } - }) - }); - }); - } - } Self::register_inner(workspace); } pub fn new(cx: &mut ViewContext) -> Self { From ad20bc39c5fcf23018fe3fa9d616675b069db579 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Jan 2024 15:31:00 -0800 Subject: [PATCH 50/67] Fix accidental load of default keymap *after* loading user keymap Co-authored-by: Mikayla Co-authored-by: Marshall --- crates/zed/src/zed.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index fea84c296419c2a7f41a538ec026cdececeee99f..c7d30230ea035b29121ceb487e4ea3ce483d5d54 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -400,7 +400,6 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { }); workspace.focus_handle(cx).focus(cx); - load_default_keymap(cx); }) .detach(); } @@ -571,6 +570,8 @@ pub fn handle_keymap_file_changes( }) .detach(); + load_default_keymap(cx); + cx.spawn(move |cx| async move { let mut user_keymap = KeymapFile::default(); loop { From 4a7233f8f01d68d38b75be2f1997e5966e866e18 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 4 Jan 2024 19:43:12 -0700 Subject: [PATCH 51/67] Fixy fix --- crates/collab/src/tests/editor_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index c6577014618cfd7fec29c76fb97cf8a10d14a617..6f06e9f10faadf77d44eecbf43f6026c2799adca 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -326,8 +326,8 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu editor_b.update(&mut cx_b, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([13..13])); editor.handle_input(".", cx); - editor_b.focus_handle(cx).focus(cx); }); + cx_b.focus_view(&editor_b); // Receive a completion request as the host's language server. // Return some completions from the host's language server. From 5037cca7ece59844beb2f5ce942b1be31cac0f89 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 4 Jan 2024 20:16:25 -0700 Subject: [PATCH 52/67] Add LeakDetector for gpui2 Co-Authored-By: Julia --- crates/gpui/src/app/entity_map.rs | 124 +++++++++++++++++++++++++++++- crates/gpui/src/view.rs | 5 ++ 2 files changed, 126 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index f71cfbdebcd615daaa5758132d59f73aadd7cacd..3bab9d00f90db20ef8216cffbc118a848f5eec00 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -1,6 +1,8 @@ use crate::{private::Sealed, AppContext, Context, Entity, ModelContext}; use anyhow::{anyhow, Result}; +use collections::HashMap; use derive_more::{Deref, DerefMut}; +use lazy_static::lazy_static; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; use slotmap::{SecondaryMap, SlotMap}; use std::{ @@ -38,6 +40,8 @@ pub(crate) struct EntityMap { struct EntityRefCounts { counts: SlotMap, dropped_entity_ids: Vec, + #[cfg(any(test, feature = "test-support"))] + leak_detector: LeakDetector, } impl EntityMap { @@ -47,6 +51,11 @@ impl EntityMap { ref_counts: Arc::new(RwLock::new(EntityRefCounts { counts: SlotMap::with_key(), dropped_entity_ids: Vec::new(), + #[cfg(any(test, feature = "test-support"))] + leak_detector: LeakDetector { + next_handle_id: 0, + entity_handles: HashMap::default(), + }, })), } } @@ -156,6 +165,8 @@ pub struct AnyModel { pub(crate) entity_id: EntityId, pub(crate) entity_type: TypeId, entity_map: Weak>, + #[cfg(any(test, feature = "test-support"))] + handle_id: HandleId, } impl AnyModel { @@ -163,7 +174,14 @@ impl AnyModel { Self { entity_id: id, entity_type, - entity_map, + entity_map: entity_map.clone(), + #[cfg(any(test, feature = "test-support"))] + handle_id: entity_map + .upgrade() + .unwrap() + .write() + .leak_detector + .handle_created(id), } } @@ -207,11 +225,20 @@ impl Clone for AnyModel { assert_ne!(prev_count, 0, "Detected over-release of a model."); } - Self { + let this = Self { entity_id: self.entity_id, entity_type: self.entity_type, entity_map: self.entity_map.clone(), - } + #[cfg(any(test, feature = "test-support"))] + handle_id: self + .entity_map + .upgrade() + .unwrap() + .write() + .leak_detector + .handle_created(self.entity_id), + }; + this } } @@ -231,6 +258,14 @@ impl Drop for AnyModel { entity_map.dropped_entity_ids.push(self.entity_id); } } + + #[cfg(any(test, feature = "test-support"))] + if let Some(entity_map) = self.entity_map.upgrade() { + entity_map + .write() + .leak_detector + .handle_dropped(self.entity_id, self.handle_id) + } } } @@ -423,13 +458,43 @@ impl AnyWeakModel { return None; } ref_count.fetch_add(1, SeqCst); + drop(ref_counts); Some(AnyModel { entity_id: self.entity_id, entity_type: self.entity_type, entity_map: self.entity_ref_counts.clone(), + #[cfg(any(test, feature = "test-support"))] + handle_id: self + .entity_ref_counts + .upgrade() + .unwrap() + .write() + .leak_detector + .handle_created(self.entity_id), }) } + + #[cfg(any(test, feature = "test-support"))] + pub fn assert_dropped(&self) { + self.entity_ref_counts + .upgrade() + .unwrap() + .write() + .leak_detector + .assert_dropped(self.entity_id); + + if self + .entity_ref_counts + .upgrade() + .and_then(|ref_counts| Some(ref_counts.read().counts.get(self.entity_id)?.load(SeqCst))) + .is_some() + { + panic!( + "entity was recently dropped but resources are retained until the end of the effect cycle." + ) + } + } } impl From> for AnyWeakModel { @@ -534,6 +599,59 @@ impl PartialEq> for WeakModel { } } +#[cfg(any(test, feature = "test-support"))] +lazy_static! { + static ref LEAK_BACKTRACE: bool = + std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty()); +} + +#[cfg(any(test, feature = "test-support"))] +#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)] +pub struct HandleId { + id: u64, // id of the handle itself, not the pointed at object +} + +#[cfg(any(test, feature = "test-support"))] +pub struct LeakDetector { + next_handle_id: u64, + entity_handles: HashMap>>, +} + +#[cfg(any(test, feature = "test-support"))] +impl LeakDetector { + #[track_caller] + pub fn handle_created(&mut self, entity_id: EntityId) -> HandleId { + let id = util::post_inc(&mut self.next_handle_id); + let handle_id = HandleId { id }; + let handles = self.entity_handles.entry(entity_id).or_default(); + handles.insert( + handle_id, + LEAK_BACKTRACE.then(|| backtrace::Backtrace::new_unresolved()), + ); + handle_id + } + + pub fn handle_dropped(&mut self, entity_id: EntityId, handle_id: HandleId) { + let handles = self.entity_handles.entry(entity_id).or_default(); + handles.remove(&handle_id); + } + + pub fn assert_dropped(&mut self, entity_id: EntityId) { + let handles = self.entity_handles.entry(entity_id).or_default(); + if !handles.is_empty() { + for (_, backtrace) in handles { + if let Some(mut backtrace) = backtrace.take() { + backtrace.resolve(); + eprintln!("Leaked handle: {:#?}", backtrace); + } else { + eprintln!("Leaked handle: export LEAK_BACKTRACE to find allocation site"); + } + } + panic!(); + } + } +} + #[cfg(test)] mod test { use crate::EntityMap; diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 6b9b452cbb196f43ca0d0b43c5a8cbc28ba3cffe..88e564d27fe27b77967a5af86da10fa3b7798ea5 100644 --- a/crates/gpui/src/view.rs +++ b/crates/gpui/src/view.rs @@ -143,6 +143,11 @@ impl WeakView { let view = self.upgrade().context("error upgrading view")?; Ok(view.update(cx, f)).flatten() } + + #[cfg(any(test, feature = "test-support"))] + pub fn assert_dropped(&self) { + self.model.assert_dropped() + } } impl Clone for WeakView { From 319bfff14e21ffdcfe4fc6440ae320ba540426d4 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 4 Jan 2024 21:34:56 -0700 Subject: [PATCH 53/67] Fix more tests broken by timing change --- crates/diagnostics/src/diagnostics.rs | 1 + crates/project_symbols/src/project_symbols.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 77e6a7673ff838f3f4a5ac3029b9d5bddacbb9ee..d89ceb62e8ce27239d0ef66f659e8e34dad1406c 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -1567,6 +1567,7 @@ mod tests { workspace::init_settings(cx); Project::init_settings(cx); crate::init(cx); + editor::init(cx); }); } diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 578a47f95ad4878af683daf187197a6d2390dffd..3c2760f720c02f411da1ef08f1d8e8bad926716f 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -395,6 +395,7 @@ mod tests { language::init(cx); Project::init_settings(cx); workspace::init_settings(cx); + editor::init(cx); }); } From fff415e3e908f55e05e8218d99163e459c35a083 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 4 Jan 2024 21:59:36 -0700 Subject: [PATCH 54/67] Improve deactivate simulation --- crates/editor/src/link_go_to_definition.rs | 5 +--- crates/gpui/src/app/test_context.rs | 15 ++++------- crates/gpui/src/platform/test/platform.rs | 22 ++++++++++++++++ crates/gpui/src/platform/test/window.rs | 29 +++------------------- crates/workspace/src/workspace.rs | 8 +++--- 5 files changed, 35 insertions(+), 44 deletions(-) diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index 42f502daeda6a308070763bb4bb9936f74aef210..04dcf9301500024230e55e30d8b20ee4b04b2c9f 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -930,10 +930,7 @@ mod tests { fn do_work() { «test»(); } "}); - // Deactivating the window dismisses the highlight - cx.update_workspace(|workspace, cx| { - workspace.on_window_activation_changed(cx); - }); + cx.cx.cx.deactivate_window(); cx.assert_editor_text_highlights::(indoc! {" fn test() { do_work(); } fn do_work() { test(); } diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index dfbffaf2e59282f2878bf3aee7cea5aeac0d09fa..a5f66be09b2f2ab15ad4e7a465876071d0b01414 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -519,16 +519,11 @@ impl<'a> VisualTestContext<'a> { self.cx.simulate_input(self.window, input) } - pub fn simulate_activation(&mut self) { - self.cx - .test_window(self.window) - .simulate_active_status_change(true) - } - - pub fn simulate_deactivation(&mut self) { - self.cx - .test_window(self.window) - .simulate_active_status_change(false) + pub fn deactivate_window(&mut self) { + if Some(self.window) == self.test_platform.active_window() { + self.test_platform.set_active_window(None) + } + self.background_executor.run_until_parked(); } } diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 889e8b971e9232ad2d64baf01104758a4af6575a..111fb839211b89c7c47b6d65f7448a92a5997675 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -79,6 +79,28 @@ impl TestPlatform { self.prompts.borrow_mut().multiple_choice.push_back(tx); rx } + + pub(crate) fn set_active_window(&self, window: Option) { + let executor = self.foreground_executor().clone(); + let previous_window = self.active_window.borrow_mut().take(); + *self.active_window.borrow_mut() = window.clone(); + + executor + .spawn(async move { + if let Some(previous_window) = previous_window { + if let Some(window) = window.as_ref() { + if Arc::ptr_eq(&previous_window.0, &window.0) { + return; + } + } + previous_window.simulate_active_status_change(false); + } + if let Some(window) = window { + window.simulate_active_status_change(true); + } + }) + .detach(); + } } // todo!("implement out what our tests needed in GPUI 1") diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index eee2cf93c76515b176a811fc0f985f4b69dc7daa..e0128d2129737dd4c057e24d61b6b8f32efdacf6 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -1,6 +1,6 @@ use crate::{ px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, InputEvent, KeyDownEvent, - Keystroke, Pixels, Platform, PlatformAtlas, PlatformDisplay, PlatformInputHandler, + Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions, }; @@ -76,7 +76,7 @@ impl TestWindow { self.0.lock().resize_callback = Some(callback); } - pub fn simulate_active_status_change(&self, active: bool) { + pub(crate) fn simulate_active_status_change(&self, active: bool) { let mut lock = self.0.lock(); let Some(mut callback) = lock.active_status_change_callback.take() else { return; @@ -184,33 +184,12 @@ impl PlatformWindow for TestWindow { } fn activate(&self) { - let this = self.clone(); - let executor = self - .0 + self.0 .lock() .platform .upgrade() .unwrap() - .foreground_executor() - .clone(); - - executor - .spawn(async move { - let state = this.0.lock(); - let platform = state.platform.upgrade().unwrap(); - let previous_window = platform.active_window.borrow_mut().replace(this.clone()); - drop(state); - drop(platform); - if let Some(previous_window) = previous_window { - if Arc::ptr_eq(&previous_window.0, &this.0) { - return; - } - previous_window.simulate_active_status_change(false); - } - - this.simulate_active_status_change(true); - }) - .detach(); + .set_active_window(Some(self.clone())) } fn set_title(&mut self, title: &str) { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 6c31366f93c663cfe8c0cd0bd5b4f9ad82ce023e..37bf224d4f3d8d5b995f26a2f1ccf56668977b77 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4763,8 +4763,7 @@ mod tests { }); // Deactivating the window saves the file. - cx.simulate_deactivation(); - cx.executor().run_until_parked(); + cx.deactivate_window(); item.update(cx, |item, _| assert_eq!(item.save_count, 1)); // Autosave on focus change. @@ -4784,14 +4783,13 @@ mod tests { item.update(cx, |item, _| assert_eq!(item.save_count, 2)); // Deactivating the window still saves the file. - cx.simulate_activation(); + cx.update(|cx| cx.activate_window()); item.update(cx, |item, cx| { cx.focus_self(); item.is_dirty = true; }); - cx.simulate_deactivation(); + cx.deactivate_window(); - cx.executor().run_until_parked(); item.update(cx, |item, _| assert_eq!(item.save_count, 3)); // Autosave after delay. From cf03ea2da9f3912a88264cf9a0bf8caf771edce2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 5 Jan 2024 10:55:33 +0100 Subject: [PATCH 55/67] Don't shift pane content when following someone --- crates/workspace/src/pane_group.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index c6eaa71663b0a8a4619e95e602b37ea85a06808a..a7368f61360ce6639dffa26ffd31f2114cd2216b 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -246,7 +246,15 @@ impl Member { .size_full() .child(pane.clone()) .when_some(leader_border, |this, color| { - this.border_2().border_color(color) + this.child( + div() + .absolute() + .size_full() + .left_0() + .top_0() + .border_2() + .border_color(color), + ) }) .when_some(leader_status_box, |this, status_box| { this.child( From 3070a6ef2674a912cc6320f48275a2f47581e7c2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 5 Jan 2024 12:38:42 +0200 Subject: [PATCH 56/67] Return back git status colors for tab labels --- crates/editor/src/items.rs | 33 ++++++++++++++++++++++++------- crates/gpui/src/app/entity_map.rs | 7 ++++--- crates/workspace/src/pane.rs | 7 ++++++- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 31c4e24659fb5a691f379e0979ce4cc5a7546aa1..9e198fa2fdca3be5efdd403cf80b22ff8beb5951 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -15,9 +15,11 @@ use language::{ proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt, Point, SelectionGoal, }; +use project::repository::GitFileStatus; use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPath}; use rpc::proto::{self, update_view, PeerId}; use settings::Settings; +use workspace::item::ItemSettings; use std::fmt::Write; use std::{ @@ -29,7 +31,7 @@ use std::{ sync::Arc, }; use text::Selection; -use theme::{ActiveTheme, Theme}; +use theme::Theme; use ui::{h_stack, prelude::*, Label}; use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt}; use workspace::{ @@ -579,7 +581,28 @@ impl Item for Editor { } fn tab_content(&self, detail: Option, selected: bool, cx: &WindowContext) -> AnyElement { - let _theme = cx.theme(); + let git_status = if ItemSettings::get_global(cx).git_status { + self.buffer() + .read(cx) + .as_singleton() + .and_then(|buffer| buffer.read(cx).project_path(cx)) + .and_then(|path| self.project.as_ref()?.read(cx).entry_for_path(&path, cx)) + .and_then(|entry| entry.git_status()) + } else { + None + }; + let label_color = match git_status { + Some(GitFileStatus::Added) => Color::Created, + Some(GitFileStatus::Modified) => Color::Modified, + Some(GitFileStatus::Conflict) => Color::Conflict, + None => { + if selected { + Color::Default + } else { + Color::Muted + } + } + }; let description = detail.and_then(|detail| { let path = path_for_buffer(&self.buffer, detail, false, cx)?; @@ -595,11 +618,7 @@ impl Item for Editor { h_stack() .gap_2() - .child(Label::new(self.title(cx).to_string()).color(if selected { - Color::Default - } else { - Color::Muted - })) + .child(Label::new(self.title(cx).to_string()).color(label_color)) .when_some(description, |this, description| { this.child( Label::new(description) diff --git a/crates/gpui/src/app/entity_map.rs b/crates/gpui/src/app/entity_map.rs index 3bab9d00f90db20ef8216cffbc118a848f5eec00..97f680560a3cabd0e7fb88e5da8ca691fd31364e 100644 --- a/crates/gpui/src/app/entity_map.rs +++ b/crates/gpui/src/app/entity_map.rs @@ -1,8 +1,6 @@ use crate::{private::Sealed, AppContext, Context, Entity, ModelContext}; use anyhow::{anyhow, Result}; -use collections::HashMap; use derive_more::{Deref, DerefMut}; -use lazy_static::lazy_static; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; use slotmap::{SecondaryMap, SlotMap}; use std::{ @@ -18,6 +16,9 @@ use std::{ thread::panicking, }; +#[cfg(any(test, feature = "test-support"))] +use collections::HashMap; + slotmap::new_key_type! { pub struct EntityId; } impl EntityId { @@ -600,7 +601,7 @@ impl PartialEq> for WeakModel { } #[cfg(any(test, feature = "test-support"))] -lazy_static! { +lazy_static::lazy_static! { static ref LEAK_BACKTRACE: bool = std::env::var("LEAK_BACKTRACE").map_or(false, |b| !b.is_empty()); } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 21c5962bebb92904935b2a2c2207cda622e29dda..91e2890adb17cfc8204b458bbd47eb049fe6e0fc 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1128,7 +1128,12 @@ impl Pane { if self.items.len() == 1 && should_activate { self.focus_handle.focus(cx); } else { - self.activate_item(index_to_activate, should_activate, should_activate, cx); + self.activate_item( + dbg!(index_to_activate), + dbg!(should_activate), + should_activate, + cx, + ); } } From a984a158fc4f1ba5a235aba3f1a4906185721afe Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 4 Jan 2024 11:39:52 +0100 Subject: [PATCH 57/67] gpui: Use async-task 4.7 --- Cargo.lock | 5 +++-- Cargo.toml | 1 - crates/gpui/Cargo.toml | 2 +- crates/gpui/src/platform/mac/dispatcher.rs | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0316c343cb4ea5922897d9fc8ab417793e770ade..32954a86aea2d0d2d083edc0f22fc30c427836d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -576,8 +576,9 @@ dependencies = [ [[package]] name = "async-task" -version = "4.0.3" -source = "git+https://github.com/zed-industries/async-task?rev=341b57d6de98cdfd7b418567b8de2022ca993a6e#341b57d6de98cdfd7b418567b8de2022ca993a6e" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" [[package]] name = "async-tls" diff --git a/Cargo.toml b/Cargo.toml index 9f2bc145901cdcfa4fc7c02649edcdbbbb73dc07..3e3c9e9999c70ad28cfb344e3f12bc6471c8ba87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -164,7 +164,6 @@ tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "31c40449749c4263a91a43593831b82229049a4c" } -async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" } # wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", rev = "v16.0.0" } # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457 diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 7cf6889054a5ed4c8a80d5aa7cd6b40b502375ba..d654131a56cbf4c8388cb754410af11cf2777812 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -19,7 +19,7 @@ gpui_macros = { path = "../gpui_macros" } util = { path = "../util" } sum_tree = { path = "../sum_tree" } sqlez = { path = "../sqlez" } -async-task = "4.0.3" +async-task = "4.7" backtrace = { version = "0.3", optional = true } ctor.workspace = true linkme = "0.3" diff --git a/crates/gpui/src/platform/mac/dispatcher.rs b/crates/gpui/src/platform/mac/dispatcher.rs index feb8925426f85e5e82e99273fab7220e59e3374f..06bef49b7a96644695874a0aff2aca43f939a209 100644 --- a/crates/gpui/src/platform/mac/dispatcher.rs +++ b/crates/gpui/src/platform/mac/dispatcher.rs @@ -11,7 +11,7 @@ use objc::{ }; use parking::{Parker, Unparker}; use parking_lot::Mutex; -use std::{ffi::c_void, sync::Arc, time::Duration}; +use std::{ffi::c_void, ptr::NonNull, sync::Arc, time::Duration}; include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs")); @@ -47,7 +47,7 @@ impl PlatformDispatcher for MacDispatcher { unsafe { dispatch_async_f( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0), - runnable.into_raw() as *mut c_void, + runnable.into_raw().as_ptr() as *mut c_void, Some(trampoline), ); } @@ -57,7 +57,7 @@ impl PlatformDispatcher for MacDispatcher { unsafe { dispatch_async_f( dispatch_get_main_queue(), - runnable.into_raw() as *mut c_void, + runnable.into_raw().as_ptr() as *mut c_void, Some(trampoline), ); } @@ -71,7 +71,7 @@ impl PlatformDispatcher for MacDispatcher { dispatch_after_f( when, queue, - runnable.into_raw() as *mut c_void, + runnable.into_raw().as_ptr() as *mut c_void, Some(trampoline), ); } @@ -91,6 +91,6 @@ impl PlatformDispatcher for MacDispatcher { } extern "C" fn trampoline(runnable: *mut c_void) { - let task = unsafe { Runnable::from_raw(runnable as *mut ()) }; + let task = unsafe { Runnable::<()>::from_raw(NonNull::new_unchecked(runnable as *mut ())) }; task.run(); } From b8539373aa18f704d4eb3d3c4ac706036d6fa3d4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 5 Jan 2024 15:54:23 +0100 Subject: [PATCH 58/67] Avoid leaking `TerminalPanel`, which would in turn leak `Project` --- crates/terminal_view/src/terminal_panel.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 82d7208ef88ba625d8430e8f5d314a9b00cb7719..32bad620ba03b0b42b59257c9af9f3686e104b38 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -53,7 +53,7 @@ pub struct TerminalPanel { impl TerminalPanel { fn new(workspace: &Workspace, cx: &mut ViewContext) -> Self { - let terminal_panel = cx.view().clone(); + let terminal_panel = cx.view().downgrade(); let pane = cx.new_view(|cx| { let mut pane = Pane::new( workspace.weak_handle(), @@ -77,14 +77,17 @@ impl TerminalPanel { pane.set_can_navigate(false, cx); pane.display_nav_history_buttons(false); pane.set_render_tab_bar_buttons(cx, move |pane, cx| { + let terminal_panel = terminal_panel.clone(); h_stack() .gap_2() .child( IconButton::new("plus", Icon::Plus) .icon_size(IconSize::Small) - .on_click(cx.listener_for(&terminal_panel, |terminal_panel, _, cx| { - terminal_panel.add_terminal(None, cx); - })) + .on_click(move |_, cx| { + terminal_panel + .update(cx, |panel, cx| panel.add_terminal(None, cx)) + .log_err(); + }) .tooltip(|cx| Tooltip::text("New Terminal", cx)), ) .child({ From bf11a04410c58e009532925da9dc76bdc3f94237 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 5 Jan 2024 17:48:07 +0200 Subject: [PATCH 59/67] Remove extra dbg!'s --- crates/workspace/src/pane.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 91e2890adb17cfc8204b458bbd47eb049fe6e0fc..21c5962bebb92904935b2a2c2207cda622e29dda 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1128,12 +1128,7 @@ impl Pane { if self.items.len() == 1 && should_activate { self.focus_handle.focus(cx); } else { - self.activate_item( - dbg!(index_to_activate), - dbg!(should_activate), - should_activate, - cx, - ); + self.activate_item(index_to_activate, should_activate, should_activate, cx); } } From c8dcc80a1f1a39ae49d03fb1a85210bf4c486d59 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 5 Jan 2024 11:17:29 -0500 Subject: [PATCH 60/67] Respect the setting to show/hide the assistant and chat panels (#3909) This PR makes it so we respect the setting to show/hide the assistant and chat panels. Resolves https://github.com/zed-industries/community/issues/2370. Release Notes: - Fixed an issue where the settings to show/hide certain panels were not respected. --- crates/assistant/src/assistant_panel.rs | 4 ++-- crates/collab_ui/src/chat_panel.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 371cc9007a8c1f2bc5fd013573294bcb024cc1d9..82ea9326e7dfff5aa85b8adcbf71d9de4f1e0d9d 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -1286,8 +1286,8 @@ impl Panel for AssistantPanel { } } - fn icon(&self, _cx: &WindowContext) -> Option { - Some(Icon::Ai) + fn icon(&self, cx: &WindowContext) -> Option { + Some(Icon::Ai).filter(|_| AssistantSettings::get_global(cx).button) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 19acb17673cedd92def75e8edf82a6258ceb940b..b142fcbe7f93146ede3d6ba6edff8e03de1540e7 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -607,8 +607,12 @@ impl Panel for ChatPanel { "ChatPanel" } - fn icon(&self, _cx: &WindowContext) -> Option { - Some(ui::Icon::MessageBubbles) + fn icon(&self, cx: &WindowContext) -> Option { + if !is_channels_feature_enabled(cx) { + return None; + } + + Some(ui::Icon::MessageBubbles).filter(|_| ChatPanelSettings::get_global(cx).button) } fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> { From 9d4d58a915e9d26f95f1b763652bb6167981bc70 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 5 Jan 2024 11:19:58 -0500 Subject: [PATCH 61/67] Implement Tinted buttons and selected_style for buttons --- crates/ui/src/components/button/button.rs | 7 +++ .../ui/src/components/button/button_icon.rs | 11 ++++ .../ui/src/components/button/button_like.rs | 62 ++++++++++++++++--- .../ui/src/components/button/icon_button.rs | 11 +++- .../ui/src/components/button/toggle_button.rs | 7 +++ crates/ui/src/prelude.rs | 2 +- 6 files changed, 88 insertions(+), 12 deletions(-) diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index 958aa66ede0809ca0fde22d09e27654aff34791e..1e60aae03b11e84eda1a2041565c6b75a5fae79f 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -92,6 +92,13 @@ impl Selectable for Button { } } +impl SelectableButton for Button { + fn selected_style(mut self, style: ButtonStyle) -> Self { + self.base = self.base.selected_style(style); + self + } +} + impl Disableable for Button { fn disabled(mut self, disabled: bool) -> Self { self.base = self.base.disabled(disabled); diff --git a/crates/ui/src/components/button/button_icon.rs b/crates/ui/src/components/button/button_icon.rs index 29b23747b22eed50a112ba2a378b9620c4021443..15538bb24d79b9f17c3fcef8c4de37466e84fc66 100644 --- a/crates/ui/src/components/button/button_icon.rs +++ b/crates/ui/src/components/button/button_icon.rs @@ -12,6 +12,7 @@ pub(super) struct ButtonIcon { disabled: bool, selected: bool, selected_icon: Option, + selected_style: Option, } impl ButtonIcon { @@ -23,6 +24,7 @@ impl ButtonIcon { disabled: false, selected: false, selected_icon: None, + selected_style: None, } } @@ -62,6 +64,13 @@ impl Selectable for ButtonIcon { } } +impl SelectableButton for ButtonIcon { + fn selected_style(mut self, style: ButtonStyle) -> Self { + self.selected_style = Some(style); + self + } +} + impl RenderOnce for ButtonIcon { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { let icon = self @@ -71,6 +80,8 @@ impl RenderOnce for ButtonIcon { let icon_color = if self.disabled { Color::Disabled + } else if self.selected_style.is_some() && self.selected { + self.selected_style.unwrap().into() } else if self.selected { Color::Selected } else { diff --git a/crates/ui/src/components/button/button_like.rs b/crates/ui/src/components/button/button_like.rs index c3d871fe154a3b3d510f23b9d7d184a88793ebd8..431286073fb33b263fd08c27ba1c07e44fe82c01 100644 --- a/crates/ui/src/components/button/button_like.rs +++ b/crates/ui/src/components/button/button_like.rs @@ -4,6 +4,10 @@ use smallvec::SmallVec; use crate::prelude::*; +pub trait SelectableButton: Selectable { + fn selected_style(self, style: ButtonStyle) -> Self; +} + pub trait ButtonCommon: Clickable + Disableable { /// A unique element ID to identify the button. fn id(&self) -> &ElementId; @@ -41,7 +45,6 @@ pub enum TintColor { #[default] Accent, Negative, - Positive, Warning, } @@ -54,17 +57,42 @@ impl TintColor { label_color: cx.theme().colors().text, icon_color: cx.theme().colors().text, }, - // TODO: Finish tint colors. - _ => ButtonLikeStyles { - background: gpui::red(), - border_color: gpui::red(), - label_color: gpui::red(), - icon_color: gpui::red(), + TintColor::Negative => ButtonLikeStyles { + background: cx.theme().status().error_background, + border_color: cx.theme().status().error_border, + label_color: cx.theme().colors().text, + icon_color: cx.theme().colors().text, + }, + TintColor::Warning => ButtonLikeStyles { + background: cx.theme().status().warning_background, + border_color: cx.theme().status().warning_border, + label_color: cx.theme().colors().text, + icon_color: cx.theme().colors().text, }, } } } +impl From for Color { + fn from(tint: TintColor) -> Self { + match tint { + TintColor::Accent => Color::Accent, + TintColor::Negative => Color::Error, + TintColor::Warning => Color::Warning, + } + } +} + +// Used to go from ButtonStyle -> Color through tint colors. +impl From for Color { + fn from(style: ButtonStyle) -> Self { + match style { + ButtonStyle::Tinted(tint) => tint.into(), + _ => Color::Default, + } + } +} + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum ButtonStyle { /// A filled button with a solid background color. Provides emphasis versus @@ -266,6 +294,7 @@ pub struct ButtonLike { pub(super) style: ButtonStyle, pub(super) disabled: bool, pub(super) selected: bool, + pub(super) selected_style: Option, pub(super) width: Option, size: ButtonSize, rounding: Option, @@ -282,6 +311,7 @@ impl ButtonLike { style: ButtonStyle::default(), disabled: false, selected: false, + selected_style: None, width: None, size: ButtonSize::Default, rounding: Some(ButtonLikeRounding::All), @@ -311,6 +341,13 @@ impl Selectable for ButtonLike { } } +impl SelectableButton for ButtonLike { + fn selected_style(mut self, style: ButtonStyle) -> Self { + self.selected_style = Some(style); + self + } +} + impl Clickable for ButtonLike { fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self { self.on_click = Some(Box::new(handler)); @@ -366,6 +403,11 @@ impl ParentElement for ButtonLike { impl RenderOnce for ButtonLike { fn render(self, cx: &mut WindowContext) -> impl IntoElement { + let style = self + .selected_style + .filter(|_| self.selected) + .unwrap_or(self.style); + self.base .h_flex() .id(self.id.clone()) @@ -384,12 +426,12 @@ impl RenderOnce for ButtonLike { ButtonSize::Default | ButtonSize::Compact => this.px_1(), ButtonSize::None => this, }) - .bg(self.style.enabled(cx).background) + .bg(style.enabled(cx).background) .when(self.disabled, |this| this.cursor_not_allowed()) .when(!self.disabled, |this| { this.cursor_pointer() - .hover(|hover| hover.bg(self.style.hovered(cx).background)) - .active(|active| active.bg(self.style.active(cx).background)) + .hover(|hover| hover.bg(style.hovered(cx).background)) + .active(|active| active.bg(style.active(cx).background)) }) .when_some( self.on_click.filter(|_| !self.disabled), diff --git a/crates/ui/src/components/button/icon_button.rs b/crates/ui/src/components/button/icon_button.rs index 0a75f361cfe10eae59c7da0d0540cac46c81e9c8..d9ed6ccb5d86a2c5122ef6cd2fd364be235566bc 100644 --- a/crates/ui/src/components/button/icon_button.rs +++ b/crates/ui/src/components/button/icon_button.rs @@ -1,6 +1,6 @@ use gpui::{AnyView, DefiniteLength}; -use crate::prelude::*; +use crate::{prelude::*, SelectableButton}; use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize}; use super::button_icon::ButtonIcon; @@ -55,6 +55,13 @@ impl Selectable for IconButton { } } +impl SelectableButton for IconButton { + fn selected_style(mut self, style: ButtonStyle) -> Self { + self.base = self.base.selected_style(style); + self + } +} + impl Clickable for IconButton { fn on_click( mut self, @@ -109,12 +116,14 @@ impl RenderOnce for IconButton { fn render(self, _cx: &mut WindowContext) -> impl IntoElement { let is_disabled = self.base.disabled; let is_selected = self.base.selected; + let selected_style = self.base.selected_style; self.base.child( ButtonIcon::new(self.icon) .disabled(is_disabled) .selected(is_selected) .selected_icon(self.selected_icon) + .when_some(selected_style, |this, style| this.selected_style(style)) .size(self.icon_size) .color(self.icon_color), ) diff --git a/crates/ui/src/components/button/toggle_button.rs b/crates/ui/src/components/button/toggle_button.rs index f97498b0d837539f7a46a9def47e8040d8a429dc..e458c636eceab71b889fd008ab78934b6b243e0b 100644 --- a/crates/ui/src/components/button/toggle_button.rs +++ b/crates/ui/src/components/button/toggle_button.rs @@ -63,6 +63,13 @@ impl Selectable for ToggleButton { } } +impl SelectableButton for ToggleButton { + fn selected_style(mut self, style: ButtonStyle) -> Self { + self.base.selected_style = Some(style); + self + } +} + impl Disableable for ToggleButton { fn disabled(mut self, disabled: bool) -> Self { self.base = self.base.disabled(disabled); diff --git a/crates/ui/src/prelude.rs b/crates/ui/src/prelude.rs index dbf3c79b7173c1c6d018216cb4e11aab444d2e54..63d6c4b46a498bbce2573814cd645ec1d1b7bccd 100644 --- a/crates/ui/src/prelude.rs +++ b/crates/ui/src/prelude.rs @@ -12,7 +12,7 @@ pub use crate::selectable::*; pub use crate::styles::{vh, vw}; pub use crate::visible_on_hover::*; pub use crate::{h_stack, v_stack}; -pub use crate::{Button, ButtonSize, ButtonStyle, IconButton}; +pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton}; pub use crate::{ButtonCommon, Color, StyledExt}; pub use crate::{Icon, IconElement, IconPosition, IconSize}; pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle}; From 6c4350933f768143d6616e0a90684b52dd93d2de Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Fri, 5 Jan 2024 11:20:24 -0500 Subject: [PATCH 62/67] Update titlebar call status icons --- crates/collab_ui/src/collab_titlebar_item.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 8150fe1e4ddf19bb422697d17cf0edf4ade0aed5..c013f8d43221476279c08df8eeec496f473d90e4 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -14,7 +14,7 @@ use std::sync::Arc; use theme::{ActiveTheme, PlayerColors}; use ui::{ h_stack, popover_menu, prelude::*, Avatar, Button, ButtonLike, ButtonStyle, ContextMenu, Icon, - IconButton, IconElement, Tooltip, + IconButton, IconElement, TintColor, Tooltip, }; use util::ResultExt; use vcs_menu::{build_branch_list, BranchList, OpenRecent as ToggleVcsMenu}; @@ -183,6 +183,8 @@ impl Render for CollabTitlebarItem { if is_shared { "Unshare" } else { "Share" }, ) .style(ButtonStyle::Subtle) + .selected_style(ButtonStyle::Tinted(TintColor::Accent)) + .selected(is_shared) .label_size(LabelSize::Small) .on_click(cx.listener( move |this, _, cx| { @@ -215,6 +217,7 @@ impl Render for CollabTitlebarItem { }, ) .style(ButtonStyle::Subtle) + .selected_style(ButtonStyle::Tinted(TintColor::Negative)) .icon_size(IconSize::Small) .selected(is_muted) .on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)), @@ -229,6 +232,7 @@ impl Render for CollabTitlebarItem { }, ) .style(ButtonStyle::Subtle) + .selected_style(ButtonStyle::Tinted(TintColor::Negative)) .icon_size(IconSize::Small) .selected(is_deafened) .tooltip(move |cx| { @@ -239,6 +243,7 @@ impl Render for CollabTitlebarItem { .child( IconButton::new("screen-share", ui::Icon::Screen) .style(ButtonStyle::Subtle) + .selected_style(ButtonStyle::Tinted(TintColor::Accent)) .icon_size(IconSize::Small) .selected(is_screen_sharing) .on_click(move |_, cx| { From 0083ca38e39cb187166f41791194cbc75e27b519 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 5 Jan 2024 11:34:32 -0500 Subject: [PATCH 63/67] Use the `editor_background` color for the welcome screen's background (#3910) This PR updates the welcome screen to use the same background color as the editor. Screenshot 2024-01-05 at 11 28 19 AM Release Notes: - Updated the background color of the welcome screen to match the editor background. --- crates/welcome/src/welcome.rs | 292 +++++++++++++++++----------------- 1 file changed, 149 insertions(+), 143 deletions(-) diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 4839f9791a79c1f1a266fe15a8fbcea4a65f086f..d096248a28935224de782dcdd9b62d440d50730f 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -59,153 +59,159 @@ pub struct WelcomePage { impl Render for WelcomePage { fn render(&mut self, cx: &mut gpui::ViewContext) -> impl IntoElement { - h_stack().full().track_focus(&self.focus_handle).child( - v_stack() - .w_96() - .gap_4() - .mx_auto() - .child( - svg() - .path("icons/logo_96.svg") - .text_color(gpui::white()) - .w(px(96.)) - .h(px(96.)) - .mx_auto(), - ) - .child( - h_stack() - .justify_center() - .child(Label::new("Code at the speed of thought")), - ) - .child( - v_stack() - .gap_2() - .child( - Button::new("choose-theme", "Choose a theme") - .full_width() - .on_click(cx.listener(|this, _, cx| { - this.workspace - .update(cx, |workspace, cx| { - theme_selector::toggle( - workspace, - &Default::default(), - cx, - ) - }) - .ok(); - })), - ) - .child( - Button::new("choose-keymap", "Choose a keymap") - .full_width() - .on_click(cx.listener(|this, _, cx| { - this.workspace - .update(cx, |workspace, cx| { - base_keymap_picker::toggle( - workspace, - &Default::default(), - cx, - ) - }) - .ok(); - })), - ) - .child( - Button::new("install-cli", "Install the CLI") - .full_width() - .on_click(cx.listener(|_, _, cx| { - cx.app_mut() - .spawn( - |cx| async move { install_cli::install_cli(&cx).await }, + h_stack() + .full() + .bg(cx.theme().colors().editor_background) + .track_focus(&self.focus_handle) + .child( + v_stack() + .w_96() + .gap_4() + .mx_auto() + .child( + svg() + .path("icons/logo_96.svg") + .text_color(gpui::white()) + .w(px(96.)) + .h(px(96.)) + .mx_auto(), + ) + .child( + h_stack() + .justify_center() + .child(Label::new("Code at the speed of thought")), + ) + .child( + v_stack() + .gap_2() + .child( + Button::new("choose-theme", "Choose a theme") + .full_width() + .on_click(cx.listener(|this, _, cx| { + this.workspace + .update(cx, |workspace, cx| { + theme_selector::toggle( + workspace, + &Default::default(), + cx, + ) + }) + .ok(); + })), + ) + .child( + Button::new("choose-keymap", "Choose a keymap") + .full_width() + .on_click(cx.listener(|this, _, cx| { + this.workspace + .update(cx, |workspace, cx| { + base_keymap_picker::toggle( + workspace, + &Default::default(), + cx, + ) + }) + .ok(); + })), + ) + .child( + Button::new("install-cli", "Install the CLI") + .full_width() + .on_click(cx.listener(|_, _, cx| { + cx.app_mut() + .spawn(|cx| async move { + install_cli::install_cli(&cx).await + }) + .detach_and_log_err(cx); + })), + ), + ) + .child( + v_stack() + .p_3() + .gap_2() + .bg(cx.theme().colors().elevated_surface_background) + .border_1() + .border_color(cx.theme().colors().border) + .rounded_md() + .child( + h_stack() + .gap_2() + .child( + Checkbox::new( + "enable-vim", + if VimModeSetting::get_global(cx).0 { + ui::Selection::Selected + } else { + ui::Selection::Unselected + }, ) - .detach_and_log_err(cx); - })), - ), - ) - .child( - v_stack() - .p_3() - .gap_2() - .bg(cx.theme().colors().elevated_surface_background) - .border_1() - .border_color(cx.theme().colors().border) - .rounded_md() - .child( - h_stack() - .gap_2() - .child( - Checkbox::new( - "enable-vim", - if VimModeSetting::get_global(cx).0 { - ui::Selection::Selected - } else { - ui::Selection::Unselected - }, + .on_click( + cx.listener(move |this, selection, cx| { + this.update_settings::( + selection, + cx, + |setting, value| *setting = Some(value), + ); + }), + ), ) - .on_click(cx.listener( - move |this, selection, cx| { - this.update_settings::( - selection, - cx, - |setting, value| *setting = Some(value), - ); - }, - )), - ) - .child(Label::new("Enable vim mode")), - ) - .child( - h_stack() - .gap_2() - .child( - Checkbox::new( - "enable-telemetry", - if TelemetrySettings::get_global(cx).metrics { - ui::Selection::Selected - } else { - ui::Selection::Unselected - }, + .child(Label::new("Enable vim mode")), + ) + .child( + h_stack() + .gap_2() + .child( + Checkbox::new( + "enable-telemetry", + if TelemetrySettings::get_global(cx).metrics { + ui::Selection::Selected + } else { + ui::Selection::Unselected + }, + ) + .on_click( + cx.listener(move |this, selection, cx| { + this.update_settings::( + selection, + cx, + |settings, value| { + settings.metrics = Some(value) + }, + ); + }), + ), ) - .on_click(cx.listener( - move |this, selection, cx| { - this.update_settings::( - selection, - cx, - |settings, value| settings.metrics = Some(value), - ); - }, - )), - ) - .child(Label::new("Send anonymous usage data")), - ) - .child( - h_stack() - .gap_2() - .child( - Checkbox::new( - "enable-crash", - if TelemetrySettings::get_global(cx).diagnostics { - ui::Selection::Selected - } else { - ui::Selection::Unselected - }, + .child(Label::new("Send anonymous usage data")), + ) + .child( + h_stack() + .gap_2() + .child( + Checkbox::new( + "enable-crash", + if TelemetrySettings::get_global(cx).diagnostics { + ui::Selection::Selected + } else { + ui::Selection::Unselected + }, + ) + .on_click( + cx.listener(move |this, selection, cx| { + this.update_settings::( + selection, + cx, + |settings, value| { + settings.diagnostics = Some(value) + }, + ); + }), + ), ) - .on_click(cx.listener( - move |this, selection, cx| { - this.update_settings::( - selection, - cx, - |settings, value| { - settings.diagnostics = Some(value) - }, - ); - }, - )), - ) - .child(Label::new("Send crash reports")), - ), - ), - ) + .child(Label::new("Send crash reports")), + ), + ), + ) } } From 6cc48b97ddc5723ba09bd7353dad8eb496ee183f Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 5 Jan 2024 18:41:40 +0200 Subject: [PATCH 64/67] Set a minimum size for the search input field --- crates/search/src/buffer_search.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 7cce3c71c6c1b61ee0679be22d8e4260dc73a17d..f2d019f2dc64c46f8d8d8780e9ec14af6e6243e2 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -223,6 +223,7 @@ impl Render for BufferSearchBar { .gap_2() .border_1() .border_color(editor_border) + .min_w(rems(384. / 16.)) .rounded_lg() .child(IconElement::new(Icon::MagnifyingGlass)) .child(self.render_text_input(&self.query_editor, cx)) From 319f18e9627bf0c6a998d9fbf97ee0997f9569bb Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 5 Jan 2024 11:42:40 -0500 Subject: [PATCH 65/67] Use the `editor_background` color for the project search empty state (#3911) This PR updates the project search empty state to use the same background color as the editor. Release Notes: - Updated the background color of the project search's empty state to match the editor background. --- crates/search/src/project_search.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index b6523bc3cda6e5c8e97b9c9d4d34a96db157e34d..f532ddb8e49101986169b7d389f627d17cac1dca 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -286,7 +286,6 @@ impl Render for ProjectSearchView { .size_full() .track_focus(&self.focus_handle) .child(self.results_editor.clone()) - .into_any() } else { let model = self.model.read(cx); let has_no_results = model.no_results.unwrap_or(false); @@ -363,6 +362,7 @@ impl Render for ProjectSearchView { .flex_1() .size_full() .justify_center() + .bg(cx.theme().colors().editor_background) .track_focus(&self.focus_handle) .child( h_stack() @@ -372,7 +372,6 @@ impl Render for ProjectSearchView { .child(v_stack().child(major_text).children(minor_text)) .child(h_stack().flex_1()), ) - .into_any() } } } From ea43d7a5c651f45d91987da48645a45070a4d70e Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Fri, 5 Jan 2024 11:48:52 -0500 Subject: [PATCH 66/67] Iterate on design of project search bar (#3913) This PR iterates on the design of the project search bar: - Mode selections have been updated to use `ToggleButton`s - Spacing has been added between the various elements. Release Notes: - Improved the look of the project search bar. --- crates/search/src/project_search.rs | 31 ++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index f532ddb8e49101986169b7d389f627d17cac1dca..2412921b3853b8741885d740fa5bb0b9733db61b 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -38,8 +38,8 @@ use std::{ use theme::ThemeSettings; use ui::{ - h_stack, prelude::*, v_stack, Button, Icon, IconButton, IconElement, Label, LabelCommon, - LabelSize, Selectable, Tooltip, + h_stack, prelude::*, v_stack, Icon, IconButton, IconElement, Label, LabelCommon, LabelSize, + Selectable, ToggleButton, Tooltip, }; use util::{paths::PathMatcher, ResultExt as _}; use workspace::{ @@ -1676,20 +1676,26 @@ impl Render for ProjectSearchBar { let mode_column = v_stack().items_start().justify_start().child( h_stack() + .gap_2() .child( h_stack() .child( - Button::new("project-search-text-button", "Text") + ToggleButton::new("project-search-text-button", "Text") + .style(ButtonStyle::Filled) + .size(ButtonSize::Large) .selected(search.current_mode == SearchMode::Text) .on_click(cx.listener(|this, _, cx| { this.activate_search_mode(SearchMode::Text, cx) })) .tooltip(|cx| { Tooltip::for_action("Toggle text search", &ActivateTextMode, cx) - }), + }) + .first(), ) .child( - Button::new("project-search-regex-button", "Regex") + ToggleButton::new("project-search-regex-button", "Regex") + .style(ButtonStyle::Filled) + .size(ButtonSize::Large) .selected(search.current_mode == SearchMode::Regex) .on_click(cx.listener(|this, _, cx| { this.activate_search_mode(SearchMode::Regex, cx) @@ -1700,11 +1706,20 @@ impl Render for ProjectSearchBar { &ActivateRegexMode, cx, ) + }) + .map(|this| { + if semantic_is_available { + this.middle() + } else { + this.last() + } }), ) .when(semantic_is_available, |this| { this.child( - Button::new("project-search-semantic-button", "Semantic") + ToggleButton::new("project-search-semantic-button", "Semantic") + .style(ButtonStyle::Filled) + .size(ButtonSize::Large) .selected(search.current_mode == SearchMode::Semantic) .on_click(cx.listener(|this, _, cx| { this.activate_search_mode(SearchMode::Semantic, cx) @@ -1715,7 +1730,8 @@ impl Render for ProjectSearchBar { &ActivateSemanticMode, cx, ) - }), + }) + .last(), ) }), ) @@ -1866,6 +1882,7 @@ impl Render for ProjectSearchBar { .child( h_stack() .justify_between() + .gap_2() .child(query_column) .child(mode_column) .child(replace_column) From 06ab98659957a2ed5269065331c33612381a61ce Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 5 Jan 2024 09:52:39 -0700 Subject: [PATCH 67/67] fmt --- crates/gpui/src/platform/test/window.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index e0128d2129737dd4c057e24d61b6b8f32efdacf6..f089531b0c94b556d48a1c9a8c6777b863649a10 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -1,8 +1,7 @@ use crate::{ px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, InputEvent, KeyDownEvent, - Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, - PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds, - WindowOptions, + Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, + Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions, }; use collections::HashMap; use parking_lot::Mutex;