Merge branch 'main' into channel-guests

Conrad Irwin created

Change summary

Cargo.lock                                         |  419 -
Cargo.toml                                         |    6 
assets/icons/arrow_circle.svg                      |    1 
assets/keymaps/default.json                        |    2 
crates/assistant/src/assistant_panel.rs            |   32 
crates/auto_update/src/auto_update.rs              |    2 
crates/call/src/call.rs                            |    9 
crates/client/src/telemetry.rs                     |   62 
crates/collab/src/tests/editor_tests.rs            | 3773 +++++++--------
crates/collab_ui/src/chat_panel.rs                 |    8 
crates/collab_ui/src/collab_panel.rs               |   60 
crates/collab_ui/src/collab_panel/channel_modal.rs |   10 
crates/collab_ui/src/collab_titlebar_item.rs       |   14 
crates/command_palette/src/command_palette.rs      |   16 
crates/diagnostics/src/diagnostics.rs              |   10 
crates/diagnostics/src/items.rs                    |   21 
crates/editor/src/editor.rs                        |   26 
crates/editor/src/element.rs                       |   17 
crates/editor/src/items.rs                         |   33 
crates/editor/src/link_go_to_definition.rs         |    5 
crates/feedback/Cargo.toml                         |    1 
crates/gpui/Cargo.toml                             |    2 
crates/gpui/src/app.rs                             |   16 
crates/gpui/src/app/entity_map.rs                  |  125 
crates/gpui/src/app/test_context.rs                |  135 
crates/gpui/src/elements/list.rs                   |   24 
crates/gpui/src/interactive.rs                     |    6 
crates/gpui/src/platform/mac/dispatcher.rs         |   10 
crates/gpui/src/platform/mac/shaders.metal         |   30 
crates/gpui/src/platform/test/platform.rs          |   35 
crates/gpui/src/platform/test/window.rs            |  141 
crates/gpui/src/text_system.rs                     |   41 
crates/gpui/src/view.rs                            |    5 
crates/gpui/src/window.rs                          |   46 
crates/language/src/language.rs                    |   40 
crates/language_tools/src/lsp_log.rs               |   12 
crates/language_tools/src/syntax_tree_view.rs      |   10 
crates/notifications/Cargo.toml                    |    2 
crates/notifications/src/notification_store.rs     |    0 
crates/project/src/project.rs                      |   26 
crates/project_panel/src/project_panel.rs          |   31 
crates/project_symbols/src/project_symbols.rs      |    1 
crates/search/src/buffer_search.rs                 |  128 
crates/search/src/project_search.rs                |  338 +
crates/settings/src/settings_file.rs               |   14 
crates/terminal/src/mappings/mouse.rs              |   54 
crates/terminal/src/terminal.rs                    |   25 
crates/terminal_view/Cargo.toml                    |    2 
crates/terminal_view/src/terminal_element.rs       |   55 
crates/terminal_view/src/terminal_panel.rs         |   41 
crates/terminal_view/src/terminal_view.rs          |   11 
crates/theme/src/themes/andromeda.rs               |    4 
crates/theme/src/themes/atelier.rs                 |   80 
crates/theme/src/themes/ayu.rs                     |   12 
crates/theme/src/themes/gruvbox.rs                 |   24 
crates/theme/src/themes/one.rs                     |    8 
crates/theme/src/themes/rose_pine.rs               |   12 
crates/theme/src/themes/sandcastle.rs              |    4 
crates/theme/src/themes/solarized.rs               |    8 
crates/theme/src/themes/summercamp.rs              |    4 
crates/theme_importer/src/zed1/converter.rs        |    4 
crates/theme_selector/src/theme_selector.rs        |    7 
crates/ui/docs/todo.md                             |   25 
crates/ui/src/components/button/button.rs          |    7 
crates/ui/src/components/button/button_icon.rs     |   11 
crates/ui/src/components/button/button_like.rs     |  116 
crates/ui/src/components/button/icon_button.rs     |   11 
crates/ui/src/components/button/toggle_button.rs   |    7 
crates/ui/src/components/icon.rs                   |    2 
crates/ui/src/components/tab.rs                    |    3 
crates/ui/src/prelude.rs                           |    2 
crates/ui/src/ui.rs                                |    7 
crates/welcome/src/welcome.rs                      |  292 
crates/workspace/src/pane_group.rs                 |   10 
crates/workspace/src/workspace.rs                  |   43 
crates/zed/Cargo.toml                              |    2 
crates/zed/src/app_menus.rs                        |    8 
crates/zed/src/main.rs                             |   13 
crates/zed/src/zed.rs                              |  103 
79 files changed, 3,499 insertions(+), 3,263 deletions(-)

Detailed changes

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"
@@ -582,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"
@@ -1832,105 +1827,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 +2422,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"
@@ -2597,7 +2487,6 @@ dependencies = [
  "postage",
  "project",
  "regex",
- "search",
  "serde",
  "serde_derive",
  "settings",
@@ -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"
@@ -7952,6 +7762,7 @@ dependencies = [
  "procinfo",
  "project",
  "rand 0.8.5",
+ "search",
  "serde",
  "serde_derive",
  "settings",
@@ -8524,8 +8335,6 @@ source = "git+https://github.com/tree-sitter/tree-sitter?rev=31c40449749c4263a91
 dependencies = [
  "cc",
  "regex",
- "wasmtime",
- "wasmtime-c-api-impl",
 ]
 
 [[package]]
@@ -9290,224 +9099,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"

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"
@@ -165,8 +164,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" }

assets/icons/arrow_circle.svg πŸ”—

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-refresh-cw"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/></svg>

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",

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::{
@@ -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::DivRegistrar, BufferSearchBar};
 use semantic_index::{SemanticIndex, SemanticIndexStatus};
 use settings::{Settings, SettingsStore};
 use std::{
@@ -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(
@@ -1158,7 +1156,18 @@ impl Render for AssistantPanel {
                     div()
                 });
 
+            let contents = if self.active_editor().is_some() {
+                let mut registrar = DivRegistrar::new(
+                    |panel, cx| panel.toolbar.read(cx).item_of_type::<BufferSearchBar>(),
+                    cx,
+                );
+                BufferSearchBar::register_inner(&mut registrar);
+                registrar.into_div()
+            } else {
+                div()
+            };
             v_stack()
+                .key_context("AssistantPanel")
                 .size_full()
                 .on_action(cx.listener(|this, _: &workspace::NewFile, cx| {
                     this.new_conversation(cx);
@@ -1177,7 +1186,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()
@@ -1277,8 +1286,8 @@ impl Panel for AssistantPanel {
         }
     }
 
-    fn icon(&self, _cx: &WindowContext) -> Option<Icon> {
-        Some(Icon::Ai)
+    fn icon(&self, cx: &WindowContext) -> Option<Icon> {
+        Some(Icon::Ai).filter(|_| AssistantSettings::get_global(cx).button)
     }
 
     fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
@@ -3529,12 +3538,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)
 }

crates/auto_update/src/auto_update.rs πŸ”—

@@ -270,7 +270,7 @@ impl AutoUpdater {
             ReleaseChannel::Nightly => cx
                 .try_read_global::<AppCommitSha, _>(|sha, _| release.version != sha.0)
                 .unwrap_or(true),
-            _ => release.version.parse::<SemanticVersion>()? <= current_version,
+            _ => release.version.parse::<SemanticVersion>()? > current_version,
         };
 
         if !should_download {

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,
     )
 }
 

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<Self>, cx: &mut AppContext) -> impl Future<Output = ()> {
-        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<Self>,
-        telemetry_settings: TelemetrySettings,
         file_extension: Option<String>,
         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<Self>,
-        telemetry_settings: TelemetrySettings,
         suggestion_id: Option<String>,
         suggestion_accepted: bool,
         file_extension: Option<String>,
+        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<Self>,
-        telemetry_settings: TelemetrySettings,
         conversation_id: Option<String>,
         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<Self>,
-        telemetry_settings: TelemetrySettings,
         operation: &'static str,
         room_id: Option<u64>,
         channel_id: Option<u64>,
+        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<Self>,
-        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<Self>,
-        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<Self>,
-        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<Self>,
-        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<Self>,
         event: ClickhouseEvent,
-        telemetry_settings: TelemetrySettings,
         immediate_flush: bool,
+        cx: &AppContext,
     ) {
-        if !telemetry_settings.metrics {
+        if !TelemetrySettings::get_global(cx).metrics {
             return;
         }
 

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::<Editor>()
-//         .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::<lsp::request::Completion, _, _>(|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<E>".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::<lsp::request::ResolveCompletionItem, _, _>(
-//         |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::<Editor>()
-//         .unwrap();
-
-//     let mut fake_language_server = fake_language_servers.next().await.unwrap();
-//     let mut requests = fake_language_server
-//         .handle_request::<lsp::request::CodeActionRequest, _, _>(|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::<lsp::request::CodeActionRequest, _, _>(|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::<lsp::request::CodeActionRequest>();
-
-//     // 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::<lsp::request::CodeActionResolveRequest, _, _>(
-//         |_, _| 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::<Editor>()
-//             .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::<Editor>()
-//         .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::<lsp::request::PrepareRenameRequest, _, _>(|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::<lsp::request::Rename, _, _>(|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::<Editor>()
-//             .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::notification::Progress>(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::notification::Progress>(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::<Vec<_>>(),
-//             [
-//                 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::<Vec<_>>(),
-//             [
-//                 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::<lsp::request::OnTypeFormatting, _, _>(
-//         |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::<lsp::request::OnTypeFormatting, _, _>(|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::<AllLanguageSettings>(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::<AllLanguageSettings>(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::<Editor>()
-//         .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::<lsp::request::InlayHintRequest, _, _>(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::<Editor>()
-//         .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::<lsp::request::InlayHintRefreshRequest>(())
-//         .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::<AllLanguageSettings>(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::<AllLanguageSettings>(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::<Editor>()
-//         .unwrap();
-
-//     let editor_b = workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .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::<lsp::request::InlayHintRequest, _, _>(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::<lsp::request::InlayHintRefreshRequest>(())
-//         .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<String> {
-//     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::<Editor>()
+        .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);
+    });
+    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.
+    cx_a.executor().start_waiting();
+    fake_language_server
+        .handle_request::<lsp::request::Completion, _, _>(|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<E>".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::<lsp::request::ResolveCompletionItem, _, _>(
+        |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::<Editor>()
+        .unwrap();
+
+    let mut fake_language_server = fake_language_servers.next().await.unwrap();
+    let mut requests = fake_language_server
+        .handle_request::<lsp::request::CodeActionRequest, _, _>(|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::<lsp::request::CodeActionRequest, _, _>(|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::<lsp::request::CodeActionRequest>();
+
+    // 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::<lsp::request::CodeActionResolveRequest, _, _>(
+        |_, _| 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::<Editor>()
+            .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::<Editor>()
+        .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::<lsp::request::PrepareRenameRequest, _, _>(|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::<lsp::request::Rename, _, _>(|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::<Editor>(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::notification::Progress>(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::notification::Progress>(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::<Vec<_>>(),
+            [
+                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::<Vec<_>>(),
+            [
+                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::<lsp::request::OnTypeFormatting, _, _>(
+        |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::<lsp::request::OnTypeFormatting, _, _>(|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::<AllLanguageSettings>(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::<AllLanguageSettings>(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::<Editor>()
+        .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::<lsp::request::InlayHintRequest, _, _>(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::<Editor>()
+        .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::<lsp::request::InlayHintRefreshRequest>(())
+        .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::<AllLanguageSettings>(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::<AllLanguageSettings>(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::<Editor>()
+        .unwrap();
+
+    let editor_b = workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .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::<lsp::request::InlayHintRequest, _, _>(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::<lsp::request::InlayHintRefreshRequest>(())
+        .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<String> {
+    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
+}

crates/collab_ui/src/chat_panel.rs πŸ”—

@@ -607,8 +607,12 @@ impl Panel for ChatPanel {
         "ChatPanel"
     }
 
-    fn icon(&self, _cx: &WindowContext) -> Option<ui::Icon> {
-        Some(ui::Icon::MessageBubbles)
+    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {
+        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> {

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::{
@@ -1829,15 +1829,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<Editor>,
+        cx: &mut ViewContext<Self>,
+    ) -> impl IntoElement {
+        let settings = ThemeSettings::get_global(cx);
+        let text_style = TextStyle {
+            color: if editor.read(cx).read_only(cx) {
+                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,

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()

crates/collab_ui/src/collab_titlebar_item.rs πŸ”—

@@ -15,7 +15,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};
@@ -111,6 +111,7 @@ impl Render for CollabTitlebarItem {
                                 &room,
                                 project_id,
                                 &current_user,
+                                cx,
                             ))
                             .children(
                                 remote_participants.iter().filter_map(|collaborator| {
@@ -128,6 +129,7 @@ impl Render for CollabTitlebarItem {
                                         &room,
                                         project_id,
                                         &current_user,
+                                        cx,
                                     )?;
 
                                     Some(
@@ -183,6 +185,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| {
@@ -218,6 +222,7 @@ impl Render for CollabTitlebarItem {
                                 .style(ButtonStyle::Subtle)
                                 .icon_size(IconSize::Small)
                                 .selected(is_muted)
+                                .selected_style(ButtonStyle::Tinted(TintColor::Negative))
                                 .on_click(move |_, cx| crate::toggle_mute(&Default::default(), cx)),
                             )
                         })
@@ -231,6 +236,7 @@ impl Render for CollabTitlebarItem {
                                 },
                             )
                             .style(ButtonStyle::Subtle)
+                            .selected_style(ButtonStyle::Tinted(TintColor::Negative))
                             .icon_size(IconSize::Small)
                             .selected(is_deafened)
                             .tooltip(move |cx| {
@@ -253,6 +259,7 @@ impl Render for CollabTitlebarItem {
                                     .style(ButtonStyle::Subtle)
                                     .icon_size(IconSize::Small)
                                     .selected(is_screen_sharing)
+                                    .selected_style(ButtonStyle::Tinted(TintColor::Accent))
                                     .on_click(move |_, cx| {
                                         crate::toggle_screen_sharing(&Default::default(), cx)
                                     }),
@@ -420,6 +427,7 @@ impl CollabTitlebarItem {
         room: &Room,
         project_id: Option<u64>,
         current_user: &Arc<User>,
+        cx: &ViewContext<Self>,
     ) -> Option<FacePile> {
         if room.role_for_user(user.id) == Some(proto::ChannelRole::Guest) {
             return None;
@@ -432,9 +440,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()
                     }),

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
         })
     }

crates/diagnostics/src/diagnostics.rs πŸ”—

@@ -646,8 +646,13 @@ impl Item for ProjectDiagnosticsEditor {
 
     fn tab_content(&self, _detail: Option<usize>, 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()
@@ -1572,6 +1577,7 @@ mod tests {
             workspace::init_settings(cx);
             Project::init_settings(cx);
             crate::init(cx);
+            editor::init(cx);
         });
     }
 

crates/diagnostics/src/items.rs πŸ”—

@@ -23,11 +23,21 @@ pub struct DiagnosticIndicator {
 impl Render for DiagnosticIndicator {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> 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 {

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};
@@ -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();
@@ -8451,6 +8450,12 @@ impl Editor {
             })
     }
 
+    pub fn has_background_highlights<T: 'static>(&self) -> bool {
+        self.background_highlights
+            .get(&TypeId::of::<T>())
+            .map_or(false, |(_, highlights)| !highlights.is_empty())
+    }
+
     pub fn background_highlights_in_range(
         &self,
         search_range: Range<Anchor>,
@@ -8868,14 +8873,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"))]
@@ -8913,7 +8912,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
@@ -8923,12 +8921,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,
         )
     }
 
@@ -9570,7 +9568,7 @@ impl InputHandler for Editor {
     ) -> Option<gpui::Bounds<Pixels>> {
         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

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,
@@ -1229,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;
@@ -1275,7 +1284,7 @@ impl EditorElement {
                 let background_ranges = self
                     .editor
                     .read(cx)
-                    .background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
+                    .background_highlight_row_ranges::<BufferSearchHighlights>(
                         start_anchor..end_anchor,
                         &layout.position_map.snapshot,
                         50000,
@@ -1766,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
@@ -1972,7 +1981,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::<BufferSearchHighlights>())
                     // Scrollmanager
                     || editor.scroll_manager.scrollbars_visible()
                 }
@@ -3779,7 +3788,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

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::{
@@ -580,7 +582,28 @@ impl Item for Editor {
     }
 
     fn tab_content(&self, detail: Option<usize>, 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)?;
@@ -596,11 +619,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)
@@ -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::<LinkGoToDefinitionState>(indoc! {"
                 fn test() { do_work(); }
                 fn do_work() { test(); }

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" }

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"

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<R>(&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<G: 'static, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R {
-        let mut global = self.lease_global::<G>();
-        let result = f(&mut global, self);
-        self.end_global_lease(global);
-        result
+        self.update(|cx| {
+            let mut global = cx.lease_global::<G>();
+            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<A: Action>(&mut self, listener: impl Fn(&A, &mut Self) + 'static) {
         self.global_action_listeners

crates/gpui/src/app/entity_map.rs πŸ”—

@@ -16,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 {
@@ -38,6 +41,8 @@ pub(crate) struct EntityMap {
 struct EntityRefCounts {
     counts: SlotMap<EntityId, AtomicUsize>,
     dropped_entity_ids: Vec<EntityId>,
+    #[cfg(any(test, feature = "test-support"))]
+    leak_detector: LeakDetector,
 }
 
 impl EntityMap {
@@ -47,6 +52,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 +166,8 @@ pub struct AnyModel {
     pub(crate) entity_id: EntityId,
     pub(crate) entity_type: TypeId,
     entity_map: Weak<RwLock<EntityRefCounts>>,
+    #[cfg(any(test, feature = "test-support"))]
+    handle_id: HandleId,
 }
 
 impl AnyModel {
@@ -163,7 +175,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 +226,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 +259,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 +459,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<T> From<WeakModel<T>> for AnyWeakModel {
@@ -534,6 +600,59 @@ impl<T> PartialEq<Model<T>> for WeakModel<T> {
     }
 }
 
+#[cfg(any(test, feature = "test-support"))]
+lazy_static::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<EntityId, HashMap<HandleId, Option<backtrace::Backtrace>>>,
+}
+
+#[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;

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<Pixels>) {
-        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<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
@@ -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<R>(
-        &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::<TestWindow>()
-                    .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<T: 'static>(&mut self, entity: &impl Entity<T>) -> impl Stream<Item = ()> {
@@ -563,11 +508,7 @@ impl<'a> VisualTestContext<'a> {
     }
 
     pub fn window_title(&mut self) -> Option<String> {
-        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) {
@@ -578,37 +519,11 @@ impl<'a> VisualTestContext<'a> {
         self.cx.simulate_input(self.window, input)
     }
 
-    pub fn simulate_activation(&mut self) {
-        self.simulate_window_events(&mut |handlers| {
-            handlers
-                .active_status_change
-                .iter_mut()
-                .for_each(|f| f(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());
+    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();
     }
 }
 

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<crate::Pixels>,
+        bounds: Bounds<crate::Pixels>,
         _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);

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

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();
 }

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;
+}

crates/gpui/src/platform/test/platform.rs πŸ”—

@@ -19,7 +19,7 @@ pub struct TestPlatform {
     background_executor: BackgroundExecutor,
     foreground_executor: ForegroundExecutor,
 
-    pub(crate) active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
+    pub(crate) active_window: RefCell<Option<TestWindow>>,
     active_display: Rc<dyn PlatformDisplay>,
     active_cursor: Mutex<CursorStyle>,
     current_clipboard_item: Mutex<Option<ClipboardItem>>,
@@ -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<TestWindow>) {
+        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")
@@ -130,7 +152,10 @@ impl Platform for TestPlatform {
     }
 
     fn active_window(&self) -> Option<crate::AnyWindowHandle> {
-        self.active_window.lock().clone()
+        self.active_window
+            .borrow()
+            .as_ref()
+            .map(|window| window.0.lock().handle)
     }
 
     fn open_window(
@@ -139,13 +164,13 @@ impl Platform for TestPlatform {
         options: WindowOptions,
         _draw: Box<dyn FnMut() -> Result<Scene>>,
     ) -> Box<dyn crate::PlatformWindow> {
-        *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(

crates/gpui/src/platform/test/window.rs πŸ”—

@@ -1,7 +1,7 @@
 use crate::{
-    px, AnyWindowHandle, 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, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
+    Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
 };
 use collections::HashMap;
 use parking_lot::Mutex;
@@ -10,26 +10,25 @@ use std::{
     sync::{self, Arc},
 };
 
-#[derive(Default)]
-pub(crate) struct TestWindowHandlers {
-    pub(crate) active_status_change: Vec<Box<dyn FnMut(bool)>>,
-    pub(crate) input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
-    pub(crate) moved: Vec<Box<dyn FnMut()>>,
-    pub(crate) resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
-}
-
-pub struct TestWindow {
+pub struct TestWindowState {
     pub(crate) bounds: WindowBounds,
     pub(crate) handle: AnyWindowHandle,
     display: Rc<dyn PlatformDisplay>,
     pub(crate) title: Option<String>,
     pub(crate) edited: bool,
-    pub(crate) input_handler: Option<Arc<Mutex<Box<dyn PlatformInputHandler>>>>,
-    pub(crate) handlers: Arc<Mutex<TestWindowHandlers>>,
     platform: Weak<TestPlatform>,
     sprite_atlas: Arc<dyn PlatformAtlas>,
+
+    input_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
+    active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
+    resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
+    moved_callback: Option<Box<dyn FnMut()>>,
+    input_handler: Option<Box<dyn PlatformInputHandler>>,
 }
 
+#[derive(Clone)]
+pub struct TestWindow(pub(crate) Arc<Mutex<TestWindowState>>);
+
 impl TestWindow {
     pub fn new(
         options: WindowOptions,
@@ -37,27 +36,96 @@ impl TestWindow {
         platform: Weak<TestPlatform>,
         display: Rc<dyn PlatformDisplay>,
     ) -> Self {
-        Self {
+        Self(Arc::new(Mutex::new(TestWindowState {
             bounds: options.bounds,
             display,
             platform,
             handle,
-            input_handler: None,
             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<Pixels>) {
+        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(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;
+        };
+        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<Pixels> {
-        let bounds = match self.bounds {
+        let bounds = match self.bounds() {
             WindowBounds::Fixed(bounds) => bounds,
             WindowBounds::Maximized | WindowBounds::Fullscreen => self.display().bounds(),
         };
@@ -77,7 +145,7 @@ impl PlatformWindow for TestWindow {
     }
 
     fn display(&self) -> std::rc::Rc<dyn crate::PlatformDisplay> {
-        self.display.clone()
+        self.0.lock().display.clone()
     }
 
     fn mouse_position(&self) -> Point<Pixels> {
@@ -93,11 +161,11 @@ impl PlatformWindow for TestWindow {
     }
 
     fn set_input_handler(&mut self, input_handler: Box<dyn crate::PlatformInputHandler>) {
-        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(
@@ -106,24 +174,29 @@ impl PlatformWindow for TestWindow {
         _msg: &str,
         _answers: &[&str],
     ) -> futures::channel::oneshot::Receiver<usize> {
-        self.platform.upgrade().expect("platform dropped").prompt()
+        self.0
+            .lock()
+            .platform
+            .upgrade()
+            .expect("platform dropped")
+            .prompt()
     }
 
     fn activate(&self) {
-        *self
+        self.0
+            .lock()
             .platform
             .upgrade()
-            .expect("platform dropped")
-            .active_window
-            .lock() = Some(self.handle);
+            .unwrap()
+            .set_active_window(Some(self.clone()))
     }
 
     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) {
@@ -143,15 +216,15 @@ impl PlatformWindow for TestWindow {
     }
 
     fn on_input(&self, callback: Box<dyn FnMut(crate::InputEvent) -> bool>) {
-        self.handlers.lock().input.push(callback)
+        self.0.lock().input_callback = Some(callback)
     }
 
     fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
-        self.handlers.lock().active_status_change.push(callback)
+        self.0.lock().active_status_change_callback = Some(callback)
     }
 
     fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
-        self.handlers.lock().resize.push(callback)
+        self.0.lock().resize_callback = Some(callback)
     }
 
     fn on_fullscreen(&self, _callback: Box<dyn FnMut(bool)>) {
@@ -159,7 +232,7 @@ impl PlatformWindow for TestWindow {
     }
 
     fn on_moved(&self, callback: Box<dyn FnMut()>) {
-        self.handlers.lock().moved.push(callback)
+        self.0.lock().moved_callback = Some(callback)
     }
 
     fn on_should_close(&self, _callback: Box<dyn FnMut() -> bool>) {
@@ -183,7 +256,7 @@ impl PlatformWindow for TestWindow {
     }
 
     fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
-        self.sprite_atlas.clone()
+        self.0.lock().sprite_atlas.clone()
     }
 
     fn as_test(&mut self) -> Option<&mut TestWindow> {

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<FxHashMap<RenderGlyphParams, Bounds<DevicePixels>>>,
     wrapper_pool: Mutex<FxHashMap<FontIdWithSize, Vec<LineWrapper>>>,
     font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
+    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<Pixels> {
         self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
     }
@@ -159,7 +194,7 @@ impl TextSystem {
     ) -> Result<Arc<LineLayout>> {
         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;
@@ -253,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),
                     });
                 }
 

crates/gpui/src/view.rs πŸ”—

@@ -143,6 +143,11 @@ impl<V: 'static> WeakView<V> {
         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<V> Clone for WeakView<V> {

crates/gpui/src/window.rs πŸ”—

@@ -1583,36 +1583,16 @@ impl<'a> WindowContext<'a> {
 
         let mut actions: Vec<Box<dyn Action>> = 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);
     }
 

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<Parser> = {
-        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<Language> = Arc::new(Language::new(
         LanguageConfig {
@@ -116,7 +113,6 @@ pub struct LanguageServerName(pub Arc<str>);
 pub struct CachedLspAdapter {
     pub name: LanguageServerName,
     pub short_name: &'static str,
-    pub initialization_options: Option<Value>,
     pub disk_based_diagnostic_sources: Vec<String>,
     pub disk_based_diagnostics_progress_token: Option<String>,
     pub language_ids: HashMap<String, String>,
@@ -128,7 +124,6 @@ impl CachedLspAdapter {
     pub async fn new(adapter: Arc<dyn LspAdapter>) -> Arc<Self> {
         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;
@@ -137,7 +132,6 @@ impl CachedLspAdapter {
         Arc::new(CachedLspAdapter {
             name,
             short_name,
-            initialization_options,
             disk_based_diagnostic_sources,
             disk_based_diagnostics_progress_token,
             language_ids,
@@ -641,8 +635,8 @@ enum AvailableGrammar {
         get_queries: fn(&str) -> LanguageQueries,
     },
     Wasm {
-        grammar_name: Arc<str>,
-        path: Arc<Path>,
+        _grammar_name: Arc<str>,
+        _path: Arc<Path>,
     },
 }
 
@@ -742,7 +736,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 +873,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))

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<usize>, _: bool, _: &WindowContext<'_>) -> AnyElement {
-        Label::new("LSP Logs").into_any_element()
+    fn tab_content(&self, _: Option<usize>, 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<Self>) -> Option<Box<dyn SearchableItemHandle>> {

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<usize>, _: bool, _: &WindowContext<'_>) -> AnyElement {
-        Label::new("Syntax Tree").into_any_element()
+    fn tab_content(&self, _: Option<usize>, selected: bool, _: &WindowContext<'_>) -> AnyElement {
+        Label::new("Syntax Tree")
+            .color(if selected {
+                Color::Default
+            } else {
+                Color::Muted
+            })
+            .into_any_element()
     }
 
     fn clone_on_split(

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]

crates/project/src/project.rs πŸ”—

@@ -2842,15 +2842,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({
@@ -2863,7 +2854,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(),
@@ -2984,7 +2975,7 @@ impl Project {
     async fn setup_and_insert_language_server(
         this: WeakModel<Self>,
         worktree_path: &Path,
-        initialization_options: Option<serde_json::Value>,
+        override_initialization_options: Option<serde_json::Value>,
         pending_server: PendingLanguageServer,
         adapter: Arc<CachedLspAdapter>,
         language: Arc<Language>,
@@ -2994,7 +2985,7 @@ impl Project {
     ) -> Result<Option<Arc<LanguageServer>>> {
         let language_server = Self::setup_pending_language_server(
             this.clone(),
-            initialization_options,
+            override_initialization_options,
             pending_server,
             worktree_path,
             adapter.clone(),
@@ -3024,7 +3015,7 @@ impl Project {
 
     async fn setup_pending_language_server(
         this: WeakModel<Self>,
-        initialization_options: Option<serde_json::Value>,
+        override_options: Option<serde_json::Value>,
         pending_server: PendingLanguageServer,
         worktree_path: &Path,
         adapter: Arc<CachedLspAdapter>,
@@ -3190,7 +3181,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

crates/project_panel/src/project_panel.rs πŸ”—

@@ -981,25 +981,16 @@ impl ProjectPanel {
         }
     }
 
-    fn open_in_terminal(&mut self, _: &OpenInTerminal, _cx: &mut ViewContext<Self>) {
-        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<Self>) {
+        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(
@@ -1414,7 +1405,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) {

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))
@@ -422,89 +423,134 @@ 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<A: Action>(
+        &mut self,
+        callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+    );
+}
+
+type GetSearchBar<T> =
+    for<'a, 'b> fn(&'a T, &'a mut ViewContext<'b, T>) -> Option<View<BufferSearchBar>>;
+
+/// Registers search actions on a div that can be taken out.
+pub struct DivRegistrar<'a, 'b, T: 'static> {
+    div: Option<Div>,
+    cx: &'a mut ViewContext<'b, T>,
+    search_getter: GetSearchBar<T>,
+}
+
+impl<'a, 'b, T: 'static> DivRegistrar<'a, 'b, T> {
+    pub fn new(search_getter: GetSearchBar<T>, 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<T: 'static> SearchActionsRegistrar for DivRegistrar<'_, '_, T> {
+    fn register_handler<A: gpui::Action>(
+        &mut self,
+        callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+    ) {
+        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)));
+            }))
+        });
+    }
+}
 
-            pane.update(cx, |this, cx| {
-                this.toolbar().update(cx, |this, cx| {
+/// Register actions for an active pane.
+impl SearchActionsRegistrar for Workspace {
+    fn register_handler<A: Action>(
+        &mut self,
+        callback: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
+    ) {
+        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::<BufferSearchBar>() {
-                        search_bar.update(cx, |this, cx| {
-                            this.deploy(deploy, cx);
-                        });
-                        return;
+                        search_bar.update(cx, move |this, cx| callback(this, action, cx));
+                        cx.notify();
                     }
-                    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();
                 })
             });
         });
-        fn register_action<A: Action>(
-            workspace: &mut Workspace,
-            update: fn(&mut BufferSearchBar, &A, &mut ViewContext<BufferSearchBar>),
-        ) {
-            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::<BufferSearchBar>() {
-                            search_bar.update(cx, move |this, cx| update(this, action, cx));
-                            cx.notify();
-                        }
-                    })
-                });
-            });
-        }
-
-        register_action(workspace, |this, action: &ToggleCaseSensitive, cx| {
+    }
+}
+impl BufferSearchBar {
+    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);
             }
         });
-        register_action(workspace, |this, action: &ToggleWholeWord, cx| {
+
+        registrar.register_handler(|this, action: &ToggleWholeWord, cx| {
             if this.supported_options().word {
                 this.toggle_whole_word(action, cx);
             }
         });
-        register_action(workspace, |this, action: &ToggleReplace, cx| {
+
+        registrar.register_handler(|this, action: &ToggleReplace, cx| {
             if this.supported_options().replacement {
                 this.toggle_replace(action, cx);
             }
         });
-        register_action(workspace, |this, _: &ActivateRegexMode, cx| {
+
+        registrar.register_handler(|this, _: &ActivateRegexMode, cx| {
             if this.supported_options().regex {
                 this.activate_search_mode(SearchMode::Regex, cx);
             }
         });
-        register_action(workspace, |this, _: &ActivateTextMode, cx| {
+
+        registrar.register_handler(|this, _: &ActivateTextMode, cx| {
             this.activate_search_mode(SearchMode::Text, cx);
         });
-        register_action(workspace, |this, action: &CycleMode, 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)
             }
         });
-        register_action(workspace, |this, action: &SelectNextMatch, 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) {
+        Self::register_inner(workspace);
     }
     pub fn new(cx: &mut ViewContext<Self>) -> Self {
         let query_editor = cx.new_view(|cx| Editor::single_line(cx));

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::{
@@ -61,12 +61,12 @@ struct ActiveSearches(HashMap<WeakModel<Project>, WeakView<ProjectSearchView>>);
 struct ActiveSettings(HashMap<WeakModel<Project>, 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();
@@ -288,7 +288,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);
@@ -365,6 +364,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()
@@ -374,7 +374,6 @@ impl Render for ProjectSearchView {
                         .child(v_stack().child(major_text).children(minor_text))
                         .child(h_stack().flex_1()),
                 )
-                .into_any()
         }
     }
 }
@@ -943,11 +942,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<Workspace>,
+    ) {
+        let active_search = cx
+            .global::<ActiveSearches>()
+            .0
+            .get(&workspace.project().downgrade());
+        let existing = active_search
+            .and_then(|active_search| {
+                workspace
+                    .items_of_type::<ProjectSearchView>(cx)
+                    .filter(|search| &search.downgrade() == active_search)
+                    .last()
+            })
+            .or_else(|| workspace.item_of_type::<ProjectSearchView>(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<Workspace>,
+    ) {
+        Self::existing_or_new_search(workspace, None, cx)
+    }
+
+    fn existing_or_new_search(
+        workspace: &mut Workspace,
+        existing: Option<View<ProjectSearchView>>,
+        cx: &mut ViewContext<Workspace>,
     ) {
         // Clean up entries for dropped projects
         cx.update_global(|state: &mut ActiveSearches, _cx| {
@@ -964,19 +993,27 @@ impl ProjectSearchView {
             }
         });
 
-        let settings = cx
-            .global::<ActiveSettings>()
-            .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::<ActiveSettings>()
+                .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);
 
@@ -1641,20 +1678,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)
@@ -1665,11 +1708,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)
@@ -1680,7 +1732,8 @@ impl Render for ProjectSearchBar {
                                             &ActivateSemanticMode,
                                             cx,
                                         )
-                                    }),
+                                    })
+                                    .last(),
                             )
                         }),
                 )
@@ -1831,6 +1884,7 @@ impl Render for ProjectSearchBar {
             .child(
                 h_stack()
                     .justify_between()
+                    .gap_2()
                     .child(query_column)
                     .child(mode_column)
                     .child(replace_column)
@@ -2062,7 +2116,237 @@ pub mod tests {
     }
 
     #[gpui::test]
-    async fn test_project_search_focus(cx: &mut TestAppContext) {
+    async fn test_deploy_project_search_focus(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.background_executor.clone());
+        fs.insert_tree(
+            "/dir",
+            json!({
+                "one.rs": "const ONE: usize = 1;",
+                "two.rs": "const TWO: usize = one::ONE + one::ONE;",
+                "three.rs": "const THREE: usize = one::ONE + two::TWO;",
+                "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
+            }),
+        )
+        .await;
+        let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.clone();
+        let search_bar = window.build_view(cx, |_| ProjectSearchBar::new());
+
+        let active_item = cx.read(|cx| {
+            workspace
+                .read(cx)
+                .unwrap()
+                .active_pane()
+                .read(cx)
+                .active_item()
+                .and_then(|item| item.downcast::<ProjectSearchView>())
+        });
+        assert!(
+            active_item.is_none(),
+            "Expected no search panel to be active"
+        );
+
+        window
+            .update(cx, move |workspace, cx| {
+                assert_eq!(workspace.panes().len(), 1);
+                workspace.panes()[0].update(cx, move |pane, cx| {
+                    pane.toolbar()
+                        .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
+                });
+
+                ProjectSearchView::deploy_search(workspace, &workspace::DeploySearch, cx)
+            })
+            .unwrap();
+
+        let Some(search_view) = cx.read(|cx| {
+            workspace
+                .read(cx)
+                .unwrap()
+                .active_pane()
+                .read(cx)
+                .active_item()
+                .and_then(|item| item.downcast::<ProjectSearchView>())
+        }) 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());
@@ -2103,7 +2387,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();
 
@@ -2252,7 +2536,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();
@@ -2538,7 +2822,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();

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<T: Settings>(
     })
     .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::<BaseKeymap>(cx).asset_path() {
-    //     KeymapFile::load_asset(asset_path, cx).unwrap();
-    // }
-}

crates/terminal/src/mappings/mouse.rs πŸ”—

@@ -158,39 +158,41 @@ pub fn mouse_moved_report(point: AlacPoint, e: &MouseMoveEvent, mode: TermMode)
     }
 }
 
-pub fn mouse_side(
+pub fn grid_point(pos: Point<Pixels>, 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<Pixels>,
     cur_size: TerminalSize,
-) -> alacritty_terminal::index::Direction {
-    let cell_width = cur_size.cell_width.floor();
-    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 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
-    {
+    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 mut side = if cell_x > half_cell_width {
         Side::Right
     } else {
         Side::Left
-    }
-}
+    };
 
-pub fn grid_point(pos: Point<Pixels>, 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

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;
@@ -61,15 +62,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
@@ -704,14 +697,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 +1079,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 +1165,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),

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" }

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;
@@ -421,7 +422,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
@@ -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| {

crates/terminal_view/src/terminal_panel.rs πŸ”—

@@ -3,11 +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, Entity, EventEmitter, ExternalPaths,
+    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::DivRegistrar, BufferSearchBar};
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
 use terminal::terminal_settings::{TerminalDockPosition, TerminalSettings};
@@ -52,7 +53,7 @@ pub struct TerminalPanel {
 
 impl TerminalPanel {
     fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> 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(),
@@ -76,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({
@@ -101,9 +105,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.new_view(search::BufferSearchBar::new);
+            pane.toolbar()
+                .update(cx, |toolbar, cx| toolbar.add_item(buffer_search_bar, cx));
             pane
         });
         let subscriptions = vec![
@@ -329,8 +333,20 @@ impl TerminalPanel {
 impl EventEmitter<PanelEvent> for TerminalPanel {}
 
 impl Render for TerminalPanel {
-    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-        div().size_full().child(self.pane.clone())
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        let mut registrar = DivRegistrar::new(
+            |panel, cx| {
+                panel
+                    .pane
+                    .read(cx)
+                    .toolbar()
+                    .read(cx)
+                    .item_of_type::<BufferSearchBar>()
+            },
+            cx,
+        );
+        BufferSearchBar::register_inner(&mut registrar);
+        registrar.into_div().size_full().child(self.pane.clone())
     }
 }
 
@@ -410,11 +426,6 @@ impl Panel for TerminalPanel {
         "TerminalPanel"
     }
 
-    // todo!()
-    // fn icon_tooltip(&self) -> (String, Option<Box<dyn Action>>) {
-    //     ("Terminal Panel".into(), Some(Box::new(ToggleFocus)))
-    // }
-
     fn icon(&self, _cx: &WindowContext) -> Option<Icon> {
         Some(Icon::Terminal)
     }

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,
 };
 
@@ -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);
@@ -714,10 +714,9 @@ impl Item for TerminalView {
         false
     }
 
-    // todo!(search)
-    // fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
-    //     Some(Box::new(handle.clone()))
-    // }
+    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+        Some(Box::new(handle.clone()))
+    }
 
     fn breadcrumb_location(&self) -> ToolbarItemLocation {
         ToolbarItemLocation::PrimaryLeft

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()),
@@ -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()),

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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),

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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),

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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),

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()),
@@ -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()),
@@ -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()),
@@ -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()),

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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),
@@ -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()),

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()),
@@ -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()),

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()),
@@ -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()),
@@ -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()),
@@ -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()),

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()),
@@ -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()),

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()),
@@ -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),

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::<ThemeSettings>(self.fs.clone(), cx, move |settings| {
             settings.theme = Some(theme_name.to_string());

crates/ui/docs/todo.md πŸ”—

@@ -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

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);

crates/ui/src/components/button/button_icon.rs πŸ”—

@@ -12,6 +12,7 @@ pub(super) struct ButtonIcon {
     disabled: bool,
     selected: bool,
     selected_icon: Option<Icon>,
+    selected_style: Option<ButtonStyle>,
 }
 
 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 {

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;
@@ -36,17 +40,68 @@ pub enum IconPosition {
     End,
 }
 
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
+pub enum TintColor {
+    #[default]
+    Accent,
+    Negative,
+    Warning,
+}
+
+impl TintColor {
+    fn button_like_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,
+            },
+            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<TintColor> 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<ButtonStyle> for Color {
+    fn from(style: ButtonStyle) -> Self {
+        match style {
+            ButtonStyle::Tinted(tint) => tint.into(),
+            _ => Color::Default,
+        }
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
 pub enum ButtonStyle {
     /// A filled button with a solid background color. Provides emphasis versus
     /// 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,12 +141,7 @@ impl ButtonStyle {
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
-            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(),
@@ -115,12 +165,7 @@ impl ButtonStyle {
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
-            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(),
@@ -146,12 +191,7 @@ impl ButtonStyle {
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
-            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(),
@@ -178,12 +218,7 @@ impl ButtonStyle {
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
-            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,
@@ -208,12 +243,7 @@ impl ButtonStyle {
                 label_color: Color::Disabled.color(cx),
                 icon_color: Color::Disabled.color(cx),
             },
-            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,
@@ -264,6 +294,7 @@ pub struct ButtonLike {
     pub(super) style: ButtonStyle,
     pub(super) disabled: bool,
     pub(super) selected: bool,
+    pub(super) selected_style: Option<ButtonStyle>,
     pub(super) width: Option<DefiniteLength>,
     size: ButtonSize,
     rounding: Option<ButtonLikeRounding>,
@@ -280,6 +311,7 @@ impl ButtonLike {
             style: ButtonStyle::default(),
             disabled: false,
             selected: false,
+            selected_style: None,
             width: None,
             size: ButtonSize::Default,
             rounding: Some(ButtonLikeRounding::All),
@@ -309,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));
@@ -364,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())
@@ -382,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),

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),
         )

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);

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",

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("")

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};

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;

crates/welcome/src/welcome.rs πŸ”—

@@ -59,153 +59,159 @@ pub struct WelcomePage {
 
 impl Render for WelcomePage {
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> 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::<VimModeSetting>(
+                                                    selection,
+                                                    cx,
+                                                    |setting, value| *setting = Some(value),
+                                                );
+                                            }),
+                                        ),
                                     )
-                                    .on_click(cx.listener(
-                                        move |this, selection, cx| {
-                                            this.update_settings::<VimModeSetting>(
-                                                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::<TelemetrySettings>(
+                                                    selection,
+                                                    cx,
+                                                    |settings, value| {
+                                                        settings.metrics = Some(value)
+                                                    },
+                                                );
+                                            }),
+                                        ),
                                     )
-                                    .on_click(cx.listener(
-                                        move |this, selection, cx| {
-                                            this.update_settings::<TelemetrySettings>(
-                                                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::<TelemetrySettings>(
+                                                    selection,
+                                                    cx,
+                                                    |settings, value| {
+                                                        settings.diagnostics = Some(value)
+                                                    },
+                                                );
+                                            }),
+                                        ),
                                     )
-                                    .on_click(cx.listener(
-                                        move |this, selection, cx| {
-                                            this.update_settings::<TelemetrySettings>(
-                                                selection,
-                                                cx,
-                                                |settings, value| {
-                                                    settings.diagnostics = Some(value)
-                                                },
-                                            );
-                                        },
-                                    )),
-                                )
-                                .child(Label::new("Send crash reports")),
-                        ),
-                ),
-        )
+                                    .child(Label::new("Send crash reports")),
+                            ),
+                    ),
+            )
     }
 }
 

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(

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};
@@ -107,6 +107,7 @@ actions!(
         NewCenterTerminal,
         ToggleTerminalFocus,
         NewSearch,
+        DeploySearch,
         Feedback,
         Restart,
         Welcome,
@@ -1095,19 +1096,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)
+            });
         });
     }
 
@@ -1250,10 +1253,9 @@ impl Workspace {
     }
 
     pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
-        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,
@@ -3264,6 +3266,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,
@@ -4762,8 +4765,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.
@@ -4783,14 +4785,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.

crates/zed/Cargo.toml πŸ”—

@@ -11,7 +11,7 @@ path = "src/zed.rs"
 doctest = false
 
 [[bin]]
-name = "zed"
+name = "Zed"
 path = "src/main.rs"
 
 [dependencies]

crates/zed/src/app_menus.rs πŸ”—

@@ -150,14 +150,6 @@ pub fn app_menus() -> Vec<Menu<'static>> {
                 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",

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();
@@ -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(),

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,
@@ -64,6 +65,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<WindowBounds>,
     display_uuid: Option<Uuid>,
@@ -130,7 +138,6 @@ pub fn initialize_workspace(app_state: Arc<AppState>, 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 +214,6 @@ pub fn initialize_workspace(app_state: Arc<AppState>, 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 +223,6 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
             .register_action(|_, _: &ToggleFullScreen, cx| {
                 cx.toggle_full_screen();
             })
-            .register_action(quit)
             .register_action(|_, action: &OpenZedURL, cx| {
                 cx.global::<Arc<OpenListener>>()
                     .open_urls(&[action.url.clone()])
@@ -403,8 +400,6 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
             });
 
         workspace.focus_handle(cx).focus(cx);
-        //todo!()
-        // load_default_keymap(cx);
     })
     .detach();
 }
@@ -451,10 +446,10 @@ fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
         .detach();
 }
 
-fn quit(_: &mut Workspace, _: &Quit, cx: &mut gpui::ViewContext<Workspace>) {
+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::<Workspace>())
@@ -463,14 +458,14 @@ fn quit(_: &mut Workspace, _: &Quit, cx: &mut gpui::ViewContext<Workspace>) {
 
         // 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 +495,7 @@ fn quit(_: &mut Workspace, _: &Quit, cx: &mut gpui::ViewContext<Workspace>) {
                 }
             }
         }
-        cx.update(|_, cx| {
-            cx.quit();
-        })?;
+        cx.update(|cx| cx.quit())?;
         anyhow::Ok(())
     })
     .detach_and_log_err(cx);
@@ -564,38 +557,60 @@ pub fn handle_keymap_file_changes(
     mut user_keymap_file_rx: mpsc::UnboundedReceiver<String>,
     cx: &mut AppContext,
 ) {
+    BaseKeymap::register(cx);
+
+    let (base_keymap_tx, mut base_keymap_rx) = mpsc::unbounded();
+    let mut old_base_keymap = *BaseKeymap::get_global(cx);
+    cx.observe_global::<SettingsStore>(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();
+
+    load_default_keymap(cx);
+
     cx.spawn(move |cx| async move {
-        //  let mut settings_subscription = None;
-        while let Some(user_keymap_content) = user_keymap_file_rx.next().await {
-            if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() {
-                cx.update(|cx| reload_keymaps(cx, &keymap_content)).ok();
-
-                // todo!()
-                // let mut old_base_keymap = cx.read(|cx| *settings::get::<BaseKeymap>(cx));
-                // drop(settings_subscription);
-                // settings_subscription = Some(cx.update(|cx| {
-                //     cx.observe_global::<SettingsStore, _>(move |cx| {
-                //         let new_base_keymap = *settings::get::<BaseKeymap>(cx);
-                //         if new_base_keymap != old_base_keymap {
-                //             old_base_keymap = new_base_keymap.clone();
-                //             reload_keymaps(cx, &keymap_content);
-                //         }
-                //     })
-                // }));
+        let mut user_keymap = KeymapFile::default();
+        loop {
+            select_biased! {
+                _ = base_keymap_rx.next() => {}
+                user_keymap_content = user_keymap_file_rx.next() => {
+                    if let Some(user_keymap_content) = user_keymap_content {
+                        if let Some(keymap_content) = KeymapFile::parse(&user_keymap_content).log_err() {
+                            user_keymap = keymap_content;
+                        } else {
+                            continue
+                        }
+                    }
+                }
             }
+
+            cx.update(|cx| reload_keymaps(cx, &user_keymap)).ok();
         }
     })
     .detach();
 }
 
 fn reload_keymaps(cx: &mut AppContext, keymap_content: &KeymapFile) {
-    // todo!()
-    // cx.clear_bindings();
+    cx.clear_key_bindings();
     load_default_keymap(cx);
     keymap_content.clone().add_to_cx(cx).log_err();
     cx.set_menus(app_menus());
 }
 
+pub fn load_default_keymap(cx: &mut AppContext) {
+    for path in ["keymaps/default.json", "keymaps/vim.json"] {
+        KeymapFile::load_asset(path, cx).unwrap();
+    }
+
+    if let Some(asset_path) = BaseKeymap::get_global(cx).asset_path() {
+        KeymapFile::load_asset(asset_path, cx).unwrap();
+    }
+}
+
 fn open_local_settings_file(
     workspace: &mut Workspace,
     _: &OpenLocalSettings,