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 b04f30835c..1425e079d6 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 dba4f61793..39ab5e285b 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 e09ab0fa57..1d0af3c008 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 5449f82b2f..9f2bc14590 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 2a75050bb0..5564481c6b 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 e7a053ecf4..0000000000 --- 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 b34e66dbda..b074f10dad 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 1d0af3c008..13d4be6233 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 85a156a8eb..8ca8ba0731 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 1d0af3c008..13d4be6233 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 0000000000..750e349e2b --- /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 9f7be0c04f..da1f77b9af 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 9c0b1e58e3..4c6e48c0fc 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 e46e9cf9d0..e61ac8508f 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 75226c669b..1bd0e2e455 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 9029a01e25..995990e5a3 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 615c29dea8..44d3defbac 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 12fd7e9b66..247acd1ceb 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 966d6e5e62..5bdb89b8a6 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 9ca7a9dff2..d9660ce11e 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 945d99ed5b..acdc2b5075 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 963608d5db..d4a8de6e12 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 45563971d1..84cb760eaf 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 e61ac8508f..effdfb85f9 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 1bd0e2e455..2d05a6b65b 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 995990e5a3..a0402825c1 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 44d3defbac..b6b76bf472 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 247acd1ceb..c941fb294d 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 5bdb89b8a6..59b8a92437 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 d9660ce11e..bace9e936f 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 acdc2b5075..c925785b17 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 d4a8de6e12..2ee5f12394 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 84cb760eaf..86c40dfde5 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 7b19ad130c..4c7b183fc0 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 16d8be89af..8150fe1e4d 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 4c7b183fc0..3625b64e32 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 bc8a0809ca..c419043a72 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 789b627fb0..2391c5f3b5 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 8ca8ba0731..1e537c7554 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 44f2069694..cfb98ccd74 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 76715f69be..9e3e59b309 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 5ac850b1c6..21e6c8fa21 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 114e89f1c4..4955d01af2 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 1e537c7554..cf074bad41 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 2b5c97bac5..ab29f6c8b2 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 9e3e59b309..69e30a6ccb 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 21e6c8fa21..e0da81edc4 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 bcfdb848ab..fb85b1fc01 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 364b610eee..2a47a16741 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 1d0af3c008..13d4be6233 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 44f303ac0b..dfbffaf2e5 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 cc683cacb6..889e8b971e 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 9df513d1f7..eee2cf93c7 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 aba01b9d5b..264fa55134 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 3625b64e32..9221d87f60 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 76715f69be..6c31366f93 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 9bafb0c0bb..351c851bb9 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 f255104432..47aae89dfd 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 ab29f6c8b2..b1a3e73d6a 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 1d0af3c008..13d4be6233 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 6f438098b7..e0bee14df2 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 b15bd7c6d6..af922f109e 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 d4dea29b49..48765b67c0 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 691b83479f..a2a90d4f2f 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 5564481c6b..366d2b0098 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 b9c73ae677..a513b3907a 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 7b6541c937..71e6cb9e55 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 cf074bad41..b53fda335e 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 b1a3e73d6a..76a5d1ec5c 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 944a9b78be..60934b3959 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 8be10f9469..328a6a1c4e 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 5d70000639..3ff0db1a16 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 9a91d619a4..b6523bc3cd 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,7 @@ 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()); @@ -2101,7 +2139,237 @@ pub mod tests { .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) }); - ProjectSearchView::deploy(workspace, &workspace::NewSearch, 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()); + 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::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 69e30a6ccb..ba2a2dea90 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 60934b3959..3106a5a961 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 6fc7cfc8e8..0be917350d 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 07a4269567..c657701461 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 b7a1dbfd3d..bbc2cd4123 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 f8da622b53..4ad9540043 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 590079c51b..3a43e3f9dd 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 fb85b1fc01..fea84c2964 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, ) { - 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(); + BaseKeymap::register(cx); - // 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 (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 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 328a6a1c4e..ffdca7d813 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 e0bee14df2..6662014c46 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 77e6a7673f..9d5a62cff1 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 e38de7d373..123149eae2 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 a36264261e..c30564e9bf 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 a4b0e21d6e..2aff05d884 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 9e193e83b7..526cdf391a 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 47aae89dfd..c3d871fe15 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 a32d83d28d..19c699216a 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 19c699216a..af3e6b640f 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 b15bd7c6d6..70fceae66a 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 3ed07e5bd1..f4856b2201 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 13d4be6233..4371742056 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 110199a8d4..d848460183 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 526cdf391a..f5179d39a9 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 d4dea29b49..521efef790 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 f5179d39a9..7118d6f767 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 67aa4955bc..3521b1e849 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -422,89 +422,115 @@ impl ToolbarItemView for BufferSearchBar { } } -impl BufferSearchBar { - fn register(workspace: &mut Workspace) { - workspace.register_action(move |workspace, deploy: &Deploy, cx| { - let pane = workspace.active_pane(); +/// 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), + ); +} - 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(); - }) +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); + } }); - }); - 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 7118d6f767..f363c5228b 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 4371742056..0316c343cb 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 c6c8b9f4b2..e14df6a2d4 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 3521b1e849..afd0f6c615 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 f363c5228b..ed228faba8 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 9221d87f60..7ab6aba227 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 afd0f6c615..6e44db60c5 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 7ab6aba227..371cc9007a 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 6e44db60c5..371037e047 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 ed228faba8..82d7208ef8 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 371037e047..7cce3c71c6 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 fea84c2964..c7d30230ea 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 c657701461..6f06e9f10f 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 f71cfbdebc..3bab9d00f9 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 6b9b452cbb..88e564d27f 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 77e6a7673f..d89ceb62e8 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 578a47f95a..3c2760f720 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 42f502daed..04dcf93015 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 dfbffaf2e5..a5f66be09b 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 889e8b971e..111fb83921 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 eee2cf93c7..e0128d2129 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 6c31366f93..37bf224d4f 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 c6eaa71663..a7368f6136 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 31c4e24659..9e198fa2fd 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 3bab9d00f9..97f680560a 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 21c5962beb..91e2890adb 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 0316c343cb..32954a86ae 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 9f2bc14590..3e3c9e9999 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 7cf6889054..d654131a56 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 feb8925426..06bef49b7a 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 82d7208ef8..32bad620ba 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 91e2890adb..21c5962beb 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 371cc9007a..82ea9326e7 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 19acb17673..b142fcbe7f 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 958aa66ede..1e60aae03b 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 29b23747b2..15538bb24d 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 c3d871fe15..431286073f 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,13 +57,38 @@ 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, } } } @@ -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 0a75f361cf..d9ed6ccb5d 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 f97498b0d8..e458c636ec 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 dbf3c79b71..63d6c4b46a 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 8150fe1e4d..c013f8d432 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 4839f9791a..d096248a28 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 7cce3c71c6..f2d019f2dc 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 b6523bc3cd..f532ddb8e4 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 f532ddb8e4..2412921b38 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 e0128d2129..f089531b0c 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;